Compare commits

..

30 commits

Author SHA1 Message Date
b284e754fd chore: update iced submodule pointer 2026-05-25 11:37:02 +02:00
8a1d3cf10a yoda: use local dbus settings bindings 2026-05-25 09:55:11 +02:00
b1b8203a3b fix: gate reorderable flex row shadow constant 2026-05-24 10:01:43 +02:00
Hojjat
7dd0ee835d feat: reorderable flex row 2026-05-24 09:50:50 +02:00
675f3b59e3 chore: reduce local stack warnings 2026-05-23 20:55:59 +02:00
4743bb8ec9 yoda: cargo clippy --fix on libcosmic-yoda (115→33 warnings)
Auto-applied clippy lint suggestions across 23 files: collapse
`else { if … }` ladders, drop unneeded `return` statements, remove
needless borrows that were immediately auto-dereffed, swap `iter().next()`
for `first()`, drop redundant `.into()` to the same type, and similar
mechanical cleanups. Behavior is unchanged; the diff is -67 lines net.

The 33 remaining warnings are architectural and need manual attention
(very-complex-type aliases, too-many-arguments on internal helpers,
redundant must_use, needs-is_empty, etc.).

Leyoda 2026 – GPLv3
2026-05-05 19:10:03 +02:00
999db0a4bd yoda: cosmic-theme cleanup (4→0 warnings) — workspace at 0 warnings total
theme::Builder::build:
- #[allow(unused_assignments)] with a note explaining the pattern: the
  component_hovered/pressed_overlay seeds at the top of the function
  are overwritten by every container block (primary, secondary, …)
  before being read, which is what unused_assignments flagged.

vs_code.rs:
- Add doc + # Errors section to Theme::apply_vs_code and the
  associated Theme::reset_vs_code (was missing under #![warn(missing_docs)]).

Cargo.toml profile move:
- cosmic-theme had a [profile.dev.package] block that Cargo silently
  ignores in non-root manifests. Move the insta/similar opt-level=3
  hint to the workspace root and drop the dead block from the
  cosmic-theme manifest. Removes the cargo-level "profiles for the
  non root package will be ignored" notice that came up on every check.

Leyoda 2026 – GPLv3
2026-05-05 19:07:35 +02:00
84437e219b yoda: libcosmic-yoda dead-code purge (14→0 warnings)
Real bug fixes:
- spin_button::vertical(): was constructing the SpinButton with
  Orientation::Horizontal — vertical() rendered horizontally. Fixed
  to use Orientation::Vertical, which also resolves the
  'Vertical never constructed' warning.
- text_input/input.rs: rewrite the iterator-skip logic for trailing
  icon layout. The previous `let mut icon_layout = ...; if has_start
  ... = ...; ... = ...;` triggered two value-never-read warnings and
  obscured the intent. Now it reads as 'skip text, optionally skip
  start-icon, then take the trailing-icon layout'.

Dead code removed:
- core::Core::portal_is_high_contrast — assigned at construction,
  never read anywhere
- widget::menu::menu_bar::bar_pressed field and the
  pub fn get_mut_or_default helper — no callers
- widget::popover::State — declared, never constructed
- widget::segmented_button::widget::Focus.now — sibling of updated_at,
  never read
- widget::wayland::tooltip::widget::Variant — orphan enum (and the
  cargo fix follow-up dropped a now-unused Element import)
- widget::button::widget::Button::is_hovered method — trivial getter
  with no callers; the underlying `is_hovered` field is still used

Visibility / nits:
- segmented_button::widget::TabDragSource: gain pub(super) so the
  pub(super) field tab_drag stops exposing a more private type
- widget::menu::flex::resolve: #[allow(dead_code)] (170-line public
  helper, kept for future use)
- app/cosmic.rs: drop one unreachable arm in the surface-message match
- widget::dropdown::multi: drop unused Paragraph import

Leyoda 2026 – GPLv3
2026-05-05 19:02:31 +02:00
a9492d764c yoda: bump iced submodule → all iced crates at 0 warnings
iced_core, iced_wgpu, iced_winit, iced_widget, iced_runtime,
iced_graphics, iced_tiny_skia all now build warning-free.

Leyoda 2026 – GPLv3
2026-05-05 18:50:41 +02:00
b94c03d996 yoda: bump iced submodule → iced_widget cleanup (0 left)
Pulls in the menu wrapping bug fix and slider/lazy warning sweep.

Leyoda 2026 – GPLv3
2026-05-05 18:39:03 +02:00
301bbf6e93 yoda: bump iced submodule → iced_winit warning cleanup (0 left)
iced/yoda-wayland-only e42448770 brings iced_winit down to 0 warnings
(was 45 before the cleanup pass).

Leyoda 2026 – GPLv3
2026-05-05 18:26:51 +02:00
38a988cbd1 yoda: cargo fix on cosmic-config + bump iced auto-fix commit
cosmic-config: drop 1 unused import (Stream).
iced submodule: bump to 8a7a32ff9 (~115 trivial warnings auto-fixed
across iced_core / iced_widget / iced_runtime / iced_winit / iced_wgpu /
iced_graphics / iced_tiny_skia).

Leyoda 2026 – GPLv3
2026-05-05 16:45:49 +02:00
cdf349385b yoda: cargo fix --lib (libcosmic-yoda) — drop 99 trivial warnings
Auto-applied suggestions on libcosmic-yoda lib only: unused imports,
unused-by-name params (prefixed with _), redundant mutability. From 113
warnings down to 14 (the 14 remaining are real signals: never-read
fields, unreachable patterns, etc., to be reviewed manually).

Leyoda 2026 – GPLv3
2026-05-05 16:27:29 +02:00
282813c80e yoda: bump iced → window_clipboard via public Forgejo fork
Lets libcosmic build standalone again — no longer relies on cosmic-files'
[patch] redirect to /home/lionel/Devels/window_clipboard.

Leyoda 2026 – GPLv3
2026-05-05 12:52:52 +02:00
5117ce4799 feat(cosmic-theme): add apply_gtk_decoration_layout helper
Writes the gtk-decoration-layout key to ~/.config/gtk-{3,4}.0/settings.ini
and best-effort updates GNOME's button-layout GSettings key for apps that
still consult it. Factors out write_gtk_settings_key for reuse.

Leyoda 2026 – GPLv3
2026-05-05 09:29:47 +02:00
9b2a3643f3 Update iced warning fixes
Point the iced submodule at the commit that fixes high-signal widget and Wayland warnings. Leaves existing cosmic-theme local changes untouched.
2026-05-05 08:27:18 +02:00
388e06552f yoda: bump iced submodule → softbuffer + window_clipboard cuts
Pick up yoda-wayland-only HEAD on leyoda/iced-yoda with softbuffer +
window_clipboard workspace default-features=false — drops the last
x11 crates. See cosmic-yoterm yoda-v5 for full cut metrics.
2026-04-24 06:53:37 +02:00
c118f5a2f6 yoda: bump iced submodule ref → yoda-wayland-only HEAD 8f6be798
Picks up two commits on leyoda/iced-yoda yoda-wayland-only branch:
- winit workspace dep default-features = false (drops winit/x11 backend)
- iced_wgpu gated x11 backend behind opt-in feature

Companion to libcosmic-yoda 8d1d8739 (drop x11 defaults on iced_winit +
iced_tiny_skia) for the yoda-v4 Wayland-only cut.
2026-04-24 06:41:28 +02:00
8d1d873918 yoda: drop x11 defaults on iced_winit + iced_tiny_skia
Dep chain audit showed winit, iced_winit and iced_tiny_skia all had
"x11" in their default features. Even though our Wayland-only build
doesn't use any X11 code path, the defaults were propagating through
cargo feature unification — winit-x11, tiny-xlib, x11-dl, etc. were
all being linked.

Fix, at the dep declaration level:
- [dependencies.iced_winit] default-features = false + wayland/wayland-dlopen
- [dependencies.iced_tiny_skia] default-features = false + wayland
- iced submodule's workspace winit = { ..., default-features = false }

Result on cosmic-yoterm binary:
- size:  55.4 MB -> 53.8 MB (-1.6 MB, ~3%)
- winit symbols: 2151 -> 1340 (-811)
- x11 symbols:   1526 -> 503  (-1023)

Remaining x11 footprint (~500 symbols):
- clipboard_x11 / x11rb: window_clipboard pulls both x11 + wayland
  backends unconditionally on unix targets. Would need forking
  window_clipboard to gate x11 behind a feature.
- tiny-xlib / as-raw-xcb-connection: low-level window-handle bindings
  pulled by wgpu/softbuffer even when no X11 windows ever exist.
  Dead code at runtime, probably best left alone until wgpu/raw-window-handle
  upstream make these optional.
2026-04-24 06:00:03 +02:00
8ab7b15807 yoda-v2: color_picker Theme ref + context_menu/menu ungate winit
Two perf + correctness wins packaged as +yoda-v2 (version bump 0.1.0-yoda
-> 0.1.0-yoda.2):

1. color_picker::draw() — use the theme: &Theme parameter already passed
   to draw() instead of THEME.lock().unwrap().clone() which cloned the
   whole Theme on every redraw (src/widget/color_picker/mod.rs:622).
   Upstreamable.

2. Dropped feature = "winit" from 21 combined cfg attrs in context_menu.rs,
   menu/menu_bar.rs, menu/menu_inner.rs. These triple-gates (wayland +
   winit + surface-message) were silently disabling all context-menu and
   menubar popup creation in yoda apps (which don't activate the winit
   feature). Now the code only gates on wayland + surface-message, which
   is our actual runtime path. Matches the ungate we already did on
   surface/action.rs in Phase 3d.

cargo check --lib passes. All 4 consumer apps rebuilt + installed as
+yoda-v2 (backup of previous yoda binaries in .pre-yoda-v2 siblings).
2026-04-23 18:43:52 +02:00
aec3eb615f yoda: ungate remaining winit+wayland combined cfgs
During Phase 3d it surfaced that several widgets keep a three-clause
cfg `#[cfg(all(feature = "winit", feature = "wayland", target_os = "linux"))]`
(e.g. dropdown::widget::with_popup, widget/mod.rs menu bits,
theme/style/mod.rs). Since the yoda fork is wayland-only the "winit"
clause is vestigial — dropping it unhides these methods for Wayland
consumers (cosmic-settings needs Dropdown::with_popup on the wallpaper
page).

Also fixed a cfg asymmetry in responsive_menu_bar.rs: the fallback
block was gated `cfg(not(all(winit, wayland, linux)))` while the
primary block was `cfg(all(wayland, linux))`. With winit removed both
blocks were active and we got E0308 expected-() — aligned the cfgs so
exactly one branch compiles.
2026-04-23 17:36:21 +02:00
3e23d08728 yoda: re-apply hard rename — libcosmic → libcosmic-yoda (0.1.0-yoda)
Reverts the soft-fork pivot (6736a596). Strategy chosen with user:
full cascade fork. Every consumer (leyoda/cosmic-files + each leyoda/app)
depends on libcosmic-yoda by explicit path, so there are no transitive
deps left asking for the upstream 'libcosmic' crate — no [patch]
unification issues, no two-version traps.

Changes:
- Cargo.toml: name = libcosmic-yoda, version = 0.1.0-yoda
- i18n/*/libcosmic.ftl -> libcosmic_yoda.ftl (71 locales)
- examples/*/Cargo.toml dep refs back to libcosmic-yoda
- compat stub features (winit=[], x11=[]) kept for upstream deps that
  might still request them during the migration window

cargo check --lib passes.
2026-04-23 15:32:29 +02:00
6736a596ac yoda: soft-fork pivot — keep Cargo name "libcosmic" for dep unification
The initial hard rename (255cf7cc) broke because Cargo's [patch] with
`package = libcosmic-yoda` does NOT unify across the transitive graph.
cosmic-files (still upstream) asks for "libcosmic"; patched with a
renamed package it ends up as a separate crate, leading to two copies
of cosmic::Theme/Action with incompatible types.

Soft fork keeps the yoda identity where it counts and stays compatible:
- Cargo name  : libcosmic (for patch/unification)
- Version     : 1.0.0 (same major as upstream so [patch] semver-accepts it)
- Lib name    : cosmic (unchanged)
- Repo        : leyoda/libcosmic-yoda on Forgejo (yoda lineage)
- Branch      : main (vs upstream master)

Revert parts:
- examples/*/Cargo.toml dep refs back to libcosmic
- i18n/*/libcosmic_yoda.ftl renamed back to libcosmic.ftl

Added:
- Compat stub features: winit = [], x11 = [] — empty so Cargo can satisfy
  upstream deps asking for these, but no code is actually gated on them
  any more (all removed in Phase 2).

Ungates done to make the Wayland path self-sufficient after winit removal:
- src/lib.rs: pub mod app + pub use Application/ApplicationExt no longer
  gated on winit; prelude exports ApplicationExt unconditionally
- src/surface/action.rs: 6 functions had #[cfg(all(wayland, linux, winit))]
  triple-gates; simplified to #[cfg(all(wayland, linux))] since winit is
  no longer a meaningful gate (wayland is now the only shell)
- 12 standalone #[cfg(feature = "winit")] annotations removed from src/
  (their gated code is now always compiled)

cargo check --lib + cargo check in cosmic-yoterm both pass with a single
libcosmic v1.0.0 in the tree.
2026-04-23 15:05:31 +02:00
8701aa31d8 feat(yoda): Wayland-only cut — drop winit and x11 features
Part of the yoda fork direction: libcosmic-yoda is Wayland-only going forward.

Cargo.toml changes:
- default features: remove winit, x11, iced-wayland, multi-window -> add wayland
- applet feature: remove winit dep
- merge iced-wayland into wayland (simpler feature graph)
- remove winit, winit_debug, winit_tokio, winit_wgpu, x11, iced-wayland features
- keep iced_winit dep (it's a misnomer — also hosts the wayland/cctk runtime)

Workspace:
- examples/* excluded (many depend on winit/x11) — revisited in a later phase

Behavior:
- Library builds clean in release with default features (cargo check + cargo build --release --lib OK)
- 58 lines in src/ gated on feature "winit" become unreachable (dead-code warnings — cleanup deferred)
- 108 lines gated on feature "wayland" now always active via default

--all-features is known-broken because tokio/async-std/smol are mutually
exclusive — this is pre-existing, not a Phase 2 regression.
2026-04-23 14:29:08 +02:00
255cf7cc0b rename: libcosmic → libcosmic-yoda (fork 0.1.0-yoda)
Fork point: pop-os/libcosmic 1.0.0 + perf/quickwins-bundle + macOS
window controls feature. From here the crate diverges under the yoda
lineage with its own versioning.

Changes:
- Cargo.toml: name = libcosmic-yoda, version = 0.1.0-yoda
- [lib] name = cosmic kept unchanged — consumer code still does 'use cosmic::...'
- examples/*/Cargo.toml: updated all libcosmic dep references to libcosmic-yoda
- i18n/*/libcosmic.ftl renamed to libcosmic_yoda.ftl (71 locales) to match
  the new name expected by fluent_language_loader!() macro

cargo check --lib passes. Examples not yet validated — Phase 2 (Wayland-only
cut) will rework them anyway.
2026-04-23 07:35:22 +02:00
5c3319351c header_bar: add WindowControlsPosition (macOS-style left controls)
Adds a new public enum `WindowControlsPosition { Start, End }` and a
matching field on `HeaderBar`, allowing window controls (close / minimize
/ maximize) to be packed on the start side of the headerbar (macOS
style, icon order close → minimize → maximize) instead of the default
end side (Linux / GNOME style, minimize → maximize → close).

Wiring:
- `crate::widget::WindowControlsPosition` re-exported alongside
  `HeaderBar`.
- `HeaderBar::controls_position(Option<WindowControlsPosition>)` setter;
  when left unset, falls back to `crate::config::window_controls_position()`
  (reads `CosmicTk.window_controls_position`), mirroring how `density`
  falls back to `header_size()`.
- New `CosmicTk.window_controls_position` field with default `End` for
  backwards compatibility; serde-friendly enum so existing configs keep
  working via `#[serde(default)]` semantics.

Tested with cosmic-yoterm, cosmic-settings, cosmic-edit, cosmic-files
rebuilt against this libcosmic via a local `[patch]` override. Config
changes picked up live through the existing cosmic-config subscription.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 15:08:12 +02:00
a322516f33 segmented_button: fix internal tab reorder end-to-end
Two independent bugs prevented tab drag-and-drop reorder from working
on cosmic-comp (and likely other compositors):

1. allow_reorder required DndAction::Move to be negotiated via
   OfferEvent::SelectedAction, which cosmic-comp does not always emit
   for self-drops (the SelectedAction event either never arrives or
   arrives with DndAction::empty()). Add a fallback: accept self-drops
   whenever state.dragging_tab is set. dragging_tab is only populated
   by start_tab_drag on this same widget, so this is safe; mime match
   and on_reorder presence are checked below.

2. reorder_event_for_drop preferred drop_hint.side over positional
   swap, producing counter-intuitive no-ops: dropping A (pos 0) on the
   left half of B (pos 1) resolved to "Before B" which, after removing
   A, lands at pos 0 again — the tab appeared not to move. Always use
   default_insert_position, which derives direction from dragged vs
   target positions (Konsole/Firefox/Chrome-style swap semantics).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 11:09:46 +02:00
108441ef61 segmented_button: add on_double_click callback
Fires in addition to on_activate when the same entity is left-clicked
twice within 400 ms. Lets applications bind quick actions (e.g. rename
a tab) without blocking the normal single-click activation path.

- New Widget::on_double_click builder mirroring on_activate/on_close.
- last_click field on LocalState for timestamp tracking.
- Detection branch in the mouse handler after on_activate fires, so the
  target entity is already focused when the callback runs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 21:26:35 +02:00
1d98eee6de perf(widget): avoid VecDeque clone in segmented_button/table Model::clear
Model::clear() cloned the entire order VecDeque to iterate while
remove() mutated it, producing an O(n) allocation proportional to the
number of items — needless on a clear() which is going to drop all of
them anyway.

Replace the clone with std::mem::take(&mut self.order): we iterate the
taken VecDeque (transferring ownership), and the inner self.order.remove(index)
in each remove() call now finds position()==None and no-ops, since
self.order has been swapped with an empty default.

Same semantics, zero allocation. Noticeable on large nav/table models
(>100 items) and on apps that reset state frequently (settings pages,
file lists, context menus).
2026-04-19 16:29:02 +02:00
77262dd0af perf(malloc): throttle malloc_trim to 1 Hz in hot paths
malloc_trim(0) was called at the end of every update() and view(),
reaching 60-200 Hz during typical scrolling, resize, or animation.
Each call walks the glibc heap (10 us to several ms depending on
fragmentation) and could consume a substantial fraction of the frame
budget in worst cases.

Throttle trim() to once per second using a thread-local Instant,
preserving the existing API. RSS stays bounded (1 Hz is enough to
release collectable pages soon after) while per-frame cost becomes
a single thread-local check plus a duration comparison.

No call-site changes required; the three existing trim(0) invocations
in src/app/cosmic.rs (update, view multi-window, view single-window)
now fall under the throttle transparently.
2026-04-19 15:14:22 +02:00
117 changed files with 1091 additions and 1253 deletions

View file

@ -17,13 +17,13 @@ jobs:
- name: Install Rust nightly
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2026-04-27
toolchain: nightly-2025-07-31
- name: System dependencies
run: sudo apt-get update; sudo apt-get install -y libxkbcommon-dev libwayland-dev
- name: Build documentation
run: |
RUSTDOCFLAGS="--cfg docsrs" \
cargo +nightly-2026-04-27 doc --no-deps \
cargo +nightly-2025-07-31 doc --no-deps \
-p cosmic-client-toolkit \
-p cosmic-protocols \
-p libcosmic \

View file

@ -1,15 +0,0 @@
{
"format_on_save": "on",
"lsp": {
"rust-analyzer": {
"initialization_options": {
"check": {
"command": "clippy",
},
"rustfmt": {
"extraArgs": ["+nightly"],
},
},
},
},
}

View file

@ -7,7 +7,7 @@
name = "libcosmic-yoda"
version = "0.1.0-yoda.2"
edition = "2024"
rust-version = "1.93"
rust-version = "1.90"
[lib]
name = "cosmic"
@ -128,9 +128,9 @@ async-std = [
apply = "0.3.0"
ashpd = { version = "0.12.3", default-features = false, optional = true }
async-fs = { version = "2.2", optional = true }
async-std = { workspace = true, optional = true }
async-std = { version = "1.13", optional = true }
auto_enums = "0.8.8"
cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "c253ec1", optional = true }
cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "160b086", optional = true }
jiff = "0.2"
cosmic-config = { path = "cosmic-config" }
cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", optional = true }
@ -153,26 +153,25 @@ image-extras = { version = "0.1.0", default-features = false, features = [
"xpm",
"xbm",
], optional = true }
libc = { version = "0.2.186", optional = true }
libc = { version = "0.2.183", optional = true }
log = "0.4"
mime = { version = "0.3.17", optional = true }
palette.workspace = true
palette = "0.7.6"
rfd = { version = "0.16.0", default-features = false, features = [
"xdg-portal",
], optional = true }
rustix = { version = "1.1", features = ["pipe", "process"], optional = true }
serde = { workspace = true, features = ["derive"] }
serde = { version = "1.0.228", features = ["derive"] }
slotmap = "1.1.1"
smol = { version = "2.0.2", optional = true }
thiserror.workspace = true
thiserror = "2.0.18"
taffy = { version = "0.9.2", features = ["grid"] }
tokio = { workspace = true, optional = true }
tracing.workspace = true
unicode-segmentation = "1.13"
tokio = { version = "1.50.0", optional = true }
tracing = "0.1.44"
unicode-segmentation = "1.12"
url = "2.5.8"
zbus = { workspace = true, optional = true }
zbus = { version = "5.14.0", default-features = false, optional = true }
float-cmp = "0.10.0"
ron = { workspace = true, optional = true }
# Enable DBus feature on Linux targets
[target.'cfg(target_os = "linux")'.dependencies]
@ -248,6 +247,10 @@ git = "https://github.com/pop-os/cosmic-panel"
# path = "../cosmic-panel/cosmic-panel-config"
optional = true
[dependencies.ron]
version = "0.12"
optional = true
[workspace]
members = [
"cosmic-config",
@ -259,15 +262,7 @@ members = [
exclude = ["iced", "examples"]
[workspace.dependencies]
async-std = "1.13"
dirs = "6.0"
palette = "0.7"
ron = "0.12"
serde = "1.0"
thiserror = "2.0"
tracing = "0.1"
tokio = "1.52"
zbus = {version = "5.15", default-features = false}
dirs = "6.0.0"
# Speed up snapshot diffing in cosmic-theme tests. Cargo silently ignores
# [profile.*] blocks in non-root manifests, so this lives at the

View file

@ -20,24 +20,6 @@ While libcosmic is written entirely in Rust, some of its dependencies may requir
sudo apt install cargo cmake just libexpat1-dev libfontconfig-dev libfreetype-dev libxkbcommon-dev pkgconf
```
## Made-for-COSMIC Flatpak IDs
To identify a project as a COSMIC Application, add `<id>com.system76.CosmicApplication</id>` to the provides section of the project's metainfo.
```xml
<provides>
<id>com.system76.CosmicApplication</id>
</provides>
```
For COSMIC Applets, use `com.system76.CosmicApplet`.
```xml
<provides>
<id>com.system76.CosmicApplet</id>
</provides>
```
## Examples
Some examples are included in the [examples](./examples) directory to to kickstart your

View file

@ -15,16 +15,16 @@ zbus = { version = "5.14.0", default-features = false, optional = true }
atomicwrites = { git = "https://github.com/jackpot51/rust-atomicwrites" }
calloop = { version = "0.14.4", optional = true }
notify = "8.2.0"
ron.workspace = true
serde.workspace = true
ron = "0.12.0"
serde = "1.0.228"
cosmic-config-derive = { path = "../cosmic-config-derive/", optional = true }
iced = { path = "../iced/", default-features = false, optional = true }
iced_futures = { path = "../iced/futures/", default-features = false, optional = true }
futures-util = { version = "0.3", optional = true }
dirs.workspace = true
tokio = { workspace = true, optional = true, features = ["time"] }
async-std = { workspace = true, optional = true }
tracing.workspace = true
tokio = { version = "1.50", optional = true, features = ["time"] }
async-std = { version = "1.13", optional = true }
tracing = "0.1"
[target.'cfg(unix)'.dependencies]
xdg = "3.0"

View file

@ -1,12 +1,13 @@
use std::any::TypeId;
use std::ops::Deref;
use std::{any::TypeId, ops::Deref};
use crate::{CosmicConfigEntry, Update};
use cosmic_settings_daemon::{Changed, ConfigProxy, CosmicSettingsDaemonProxy};
use futures_util::SinkExt;
use iced_futures::futures::future::pending;
use iced_futures::futures::{self, StreamExt};
use iced_futures::{Subscription, stream};
use iced_futures::{
Subscription,
futures::{self, StreamExt, future::pending},
stream,
};
pub async fn settings_daemon_proxy() -> zbus::Result<CosmicSettingsDaemonProxy<'static>> {
let conn = zbus::Connection::session().await?;

View file

@ -1,13 +1,16 @@
//! Integrations for cosmic-config — the cosmic configuration system.
use notify::event::{EventKind, ModifyKind, RenameMode};
use notify::{RecommendedWatcher, Watcher};
use serde::Serialize;
use serde::de::DeserializeOwned;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::sync::Mutex;
use std::{env, fmt, fs};
use notify::{
RecommendedWatcher, Watcher,
event::{EventKind, ModifyKind, RenameMode},
};
use serde::{Serialize, de::DeserializeOwned};
use std::{
env, fmt, fs,
io::Write,
path::{Path, PathBuf},
sync::Mutex,
};
/// Get the config directory, with Flatpak sandbox support.
/// In Flatpak, HOST_XDG_CONFIG_HOME points to the real user config directory,
@ -51,22 +54,6 @@ fn get_state_dir() -> Option<PathBuf> {
dirs::state_dir()
}
/// Get the data directory, with Flatpak sandbox support.
fn get_data_dir() -> Option<PathBuf> {
// Check if we're running in Flatpak
if env::var_os("FLATPAK_ID").is_some() {
// Try HOST_XDG_DATA_HOME first
if let Some(host_data) = env::var_os("HOST_XDG_DATA_HOME") {
return Some(PathBuf::from(host_data));
}
// Fallback: try to construct from HOME
if let Some(home) = env::var_os("HOME") {
return Some(PathBuf::from(home).join(".local").join("share"));
}
}
dirs::data_dir()
}
#[cfg(feature = "subscription")]
mod subscription;
#[cfg(feature = "subscription")]
@ -279,24 +266,6 @@ impl Config {
})
}
/// Get data for the given application name and config version.
pub fn new_data(name: &str, version: u64) -> Result<Self, Error> {
// Look for [name]/v[version]
let path = sanitize_name(name)?.join(format!("v{}", version));
// Get libcosmic user data directory
let mut user_path = get_data_dir().ok_or(Error::NoConfigDirectory)?;
user_path.push("cosmic");
user_path.push(path);
// Create new data directory if not found.
fs::create_dir_all(&user_path)?;
Ok(Self {
system_path: None,
user_path: Some(user_path),
})
}
// Start a transaction (to set multiple configs at the same time)
#[inline]
pub fn transaction(&self) -> ConfigTransaction<'_> {

View file

@ -1,9 +1,7 @@
use iced_futures::futures::channel::mpsc;
use iced_futures::futures::{SinkExt, Stream};
use iced_futures::stream;
use iced_futures::futures::SinkExt;
use iced_futures::{futures::channel::mpsc, stream};
use notify::RecommendedWatcher;
use std::borrow::Cow;
use std::hash::Hash;
use std::{borrow::Cow, hash::Hash};
use crate::{Config, CosmicConfigEntry};
@ -79,8 +77,7 @@ async fn start_listening<T: 'static + Send + Sync + PartialEq + Clone + CosmicCo
state: ConfigState<T>,
output: &mut mpsc::Sender<crate::Update<T>>,
) -> ConfigState<T> {
use iced_futures::futures::StreamExt;
use iced_futures::futures::future::pending;
use iced_futures::futures::{StreamExt, future::pending};
match state {
ConfigState::Init(config_id, version, is_state) => {

View file

@ -15,13 +15,13 @@ export = ["serde_json"]
no-default = []
[dependencies]
palette = { workspace = true, features = ["serializing"] }
palette = { version = "0.7.6", features = ["serializing"] }
almost = "0.2"
serde = { workspace = true, features = ["derive"] }
serde = { version = "1.0.228", features = ["derive"] }
serde_json = { version = "1.0.149", optional = true, features = [
"preserve_order",
] }
ron.workspace = true
ron = "0.12.0"
csscolorparser = { version = "0.8.3", features = ["serde"] }
cosmic-config = { path = "../cosmic-config/", default-features = false, features = [
"subscription",
@ -29,7 +29,7 @@ cosmic-config = { path = "../cosmic-config/", default-features = false, features
] }
configparser = "3.1.0"
dirs.workspace = true
thiserror.workspace = true
thiserror = "2.0.18"
[dev-dependencies]
insta = "1.47.2"

View file

@ -29,51 +29,3 @@ impl Default for CornerRadii {
}
}
}
/// Roundness options for the Cosmic theme
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
pub enum Roundness {
/// Round style
#[default]
Round,
/// Slightly round style
SlightlyRound,
/// Square style
Square,
}
impl From<Roundness> for CornerRadii {
fn from(value: Roundness) -> Self {
match value {
Roundness::Round => CornerRadii::default(),
Roundness::SlightlyRound => CornerRadii {
radius_0: [0.0; 4],
radius_xs: [2.0; 4],
radius_s: [8.0; 4],
radius_m: [8.0; 4],
radius_l: [8.0; 4],
radius_xl: [8.0; 4],
},
Roundness::Square => CornerRadii {
radius_0: [0.0; 4],
radius_xs: [2.0; 4],
radius_s: [2.0; 4],
radius_m: [2.0; 4],
radius_l: [2.0; 4],
radius_xl: [2.0; 4],
},
}
}
}
impl From<CornerRadii> for Roundness {
fn from(value: CornerRadii) -> Self {
if (value.radius_m[0] - 16.0).abs() < 0.01 {
Self::Round
} else if (value.radius_m[0] - 8.0).abs() < 0.01 {
Self::SlightlyRound
} else {
Self::Square
}
}
}

View file

@ -0,0 +1,69 @@
use crate::Spacing;
use serde::{Deserialize, Serialize};
/// Density options for the Cosmic theme
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
pub enum Density {
/// Lower padding/spacing of elements
Compact,
/// Higher padding/spacing of elements
Spacious,
/// Standard padding/spacing of elements
#[default]
Standard,
}
impl From<Density> for Spacing {
fn from(value: Density) -> Self {
match value {
Density::Compact => Spacing {
space_none: 0,
space_xxxs: 4,
space_xxs: 4,
space_xs: 8,
space_s: 8,
space_m: 16,
space_l: 24,
space_xl: 32,
space_xxl: 48,
space_xxxl: 64,
},
Density::Spacious => Spacing {
space_none: 4,
space_xxxs: 8,
space_xxs: 12,
space_xs: 16,
space_s: 24,
space_m: 32,
space_l: 48,
space_xl: 64,
space_xxl: 128,
space_xxxl: 160,
},
Density::Standard => Spacing {
space_none: 0,
space_xxxs: 4,
space_xxs: 8,
space_xs: 12,
space_s: 16,
space_m: 24,
space_l: 32,
space_xl: 48,
space_xxl: 64,
space_xxxl: 128,
},
}
}
}
impl From<Spacing> for Density {
fn from(value: Spacing) -> Self {
if value.space_m.saturating_sub(16) == 0 {
Self::Compact
} else if value.space_m.saturating_sub(24) == 0 {
Self::Standard
} else {
Self::Spacious
}
}
}

View file

@ -1,5 +1,6 @@
pub use corner::*;
pub use cosmic_palette::*;
pub use density::*;
pub use derivation::*;
pub use mode::*;
pub use spacing::*;
@ -7,6 +8,7 @@ pub use theme::*;
mod corner;
mod cosmic_palette;
mod density;
mod derivation;
mod mode;
mod spacing;

View file

@ -41,59 +41,3 @@ impl Default for Spacing {
}
}
}
/// Density options for the Cosmic theme
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
pub enum Density {
/// Lower padding/spacing of elements
Compact,
/// Standard padding/spacing of elements
#[default]
Standard,
/// Higher padding/spacing of elements
Spacious,
}
impl From<Density> for Spacing {
fn from(value: Density) -> Self {
match value {
Density::Compact => Spacing {
space_none: 0,
space_xxxs: 4,
space_xxs: 4,
space_xs: 8,
space_s: 8,
space_m: 16,
space_l: 24,
space_xl: 32,
space_xxl: 48,
space_xxxl: 64,
},
Density::Standard => Spacing::default(),
Density::Spacious => Spacing {
space_none: 4,
space_xxxs: 8,
space_xxs: 12,
space_xs: 16,
space_s: 24,
space_m: 32,
space_l: 48,
space_xl: 64,
space_xxl: 128,
space_xxxl: 160,
},
}
}
}
impl From<Spacing> for Density {
fn from(value: Spacing) -> Self {
if value.space_m.saturating_sub(16) == 0 {
Self::Compact
} else if value.space_m.saturating_sub(24) == 0 {
Self::Standard
} else {
Self::Spacious
}
}
}

View file

@ -1,13 +1,13 @@
use crate::composite::over;
use crate::steps::{color_index, get_small_widget_color, get_surface_color, get_text, steps};
use crate::{
Component, Container, CornerRadii, CosmicPalette, CosmicPaletteInner, DARK_PALETTE,
LIGHT_PALETTE, NAME, Spacing, ThemeMode,
composite::over,
steps::{color_index, get_small_widget_color, get_surface_color, get_text, steps},
};
use cosmic_config::{Config, CosmicConfigEntry};
use palette::color_difference::Wcag21RelativeContrast;
use palette::rgb::Rgb;
use palette::{IntoColor, Oklcha, Srgb, Srgba, WithAlpha};
use palette::{
IntoColor, Oklcha, Srgb, Srgba, WithAlpha, color_difference::Wcag21RelativeContrast, rgb::Rgb,
};
use serde::{Deserialize, Serialize};
use std::num::NonZeroUsize;
@ -75,8 +75,6 @@ pub struct Theme {
pub icon_button: Component,
/// link button element colors
pub link_button: Component,
/// list button element colors
pub list_button: Component,
/// text button element colors
pub text_button: Component,
/// button component styling
@ -1293,15 +1291,6 @@ impl ThemeBuilder {
component.on_disabled = over(component.on.with_alpha(0.5), component.base);
component
},
list_button: Component::component(
Srgba::new(0.0, 0.0, 0.0, 0.0),
accent,
on_bg_component,
Srgba::new(0.0, 0.0, 0.0, 0.0),
button_pressed_overlay,
is_high_contrast,
control_steps_array[8],
),
success: Component::colored_component(
success,
control_steps_array[0],

View file

@ -1,6 +1,5 @@
use configparser::ini::WriteOptions;
use palette::Srgba;
use palette::rgb::Rgba;
use palette::{Srgba, rgb::Rgba};
use thiserror::Error;
use crate::Theme;

View file

@ -1,12 +1,12 @@
use crate::Theme;
use configparser::ini::Ini;
use palette::blend::Compose;
use palette::rgb::Rgba;
use palette::{Mix, Srgba, WithAlpha};
use std::fs::{self, File};
use std::io::Write;
use std::path::PathBuf;
use std::vec;
use palette::{Mix, Srgba, WithAlpha, blend::Compose, rgb::Rgba};
use std::{
fs::{self, File},
io::Write,
path::PathBuf,
vec,
};
use super::{OutputError, qt_settings_ini_style};

View file

@ -1,11 +1,12 @@
use crate::Theme;
use configparser::ini::Ini;
use cosmic_config::CosmicConfigEntry;
use palette::blend::Compose;
use palette::{Mix, Srgba};
use std::fs::{self, File};
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use palette::{Mix, Srgba, blend::Compose};
use std::{
fs::{self, File},
io::{self, Write},
path::{Path, PathBuf},
};
use super::{OutputError, qt_settings_ini_style};

View file

@ -1,8 +1,7 @@
use std::num::NonZeroUsize;
use almost::equal;
use palette::convert::FromColorUnclamped;
use palette::{ClampAssign, FromColor, Lch, Oklcha, Srgb, Srgba};
use palette::{ClampAssign, FromColor, Lch, Oklcha, Srgb, Srgba, convert::FromColorUnclamped};
/// Get an array of 100 colors with a specific hue and chroma
/// over the full range of lightness.

View file

@ -8,8 +8,7 @@ use cosmic::app::{Core, Settings, Task};
use cosmic::executor;
use cosmic::iced::{alignment, Length, Size};
use cosmic::prelude::*;
use cosmic::widget::about::About;
use cosmic::widget::{self, nav_bar};
use cosmic::widget::{self, about::About, nav_bar};
/// Runs application with these settings
#[rustfmt::skip]

View file

@ -4,8 +4,7 @@ use cosmic::iced::core::window;
use cosmic::iced::window::Id;
use cosmic::iced::{Length, Rectangle};
use cosmic::surface::action::{app_popup, destroy_popup};
use cosmic::widget::dropdown::popup_dropdown;
use cosmic::widget::{list_column, settings, toggler};
use cosmic::widget::{dropdown::popup_dropdown, list_column, settings, toggler};
use cosmic::Element;
const ID: &str = "com.system76.CosmicAppletExample";

View file

@ -5,10 +5,9 @@
use cosmic::app::Settings;
use cosmic::iced::{Alignment, Length, Size};
use cosmic::prelude::*;
use cosmic::widget::menu::{self, KeyBind};
use cosmic::widget::nav_bar;
use cosmic::{executor, iced, widget, Core};
use cosmic::{executor, iced, prelude::*, widget, Core};
use std::collections::HashMap;
use std::sync::LazyLock;
@ -240,9 +239,7 @@ impl cosmic::Application for App {
widget::progress_bar::linear::Linear::new()
.girth(10.0)
.progress(self.progress)
.width(Length::Fill)
.markers([0.25, 0.5, 0.75])
.segment_spacing(2),
.width(Length::Fill),
)
.push(
widget::progress_bar::circular::Circular::new()

View file

@ -88,7 +88,4 @@ pub fn main() {
println!("Testing state");
test_config(Config::new_state("com.system76.Example", 1).unwrap());
println!("Testing data");
test_config(Config::new_data("com.system76.Example", 1).unwrap());
}

View file

@ -23,11 +23,15 @@ use cosmic::{
Element,
};
use cosmic_time::{Instant, Timeline};
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::Arc;
use std::vec;
use std::{
cell::RefCell,
rc::Rc,
sync::{
atomic::{AtomicU32, Ordering},
Arc,
},
vec,
};
// XXX The use of button is removed because it assigns the same ID to multiple buttons, causing a crash when a11y is enabled...
// static BTN: Lazy<id::Id> = Lazy::new(|| id::Id::new("BTN"));

View file

@ -1,7 +1,9 @@
use super::{Page, Window};
use cosmic::iced::widget::{column, text};
use cosmic::widget::{list_column, settings, toggler};
use cosmic::Element;
use cosmic::{
iced::widget::{column, text},
widget::{list_column, settings, toggler},
Element,
};
#[derive(Clone, Copy, Debug)]
pub enum Message {

View file

@ -1,17 +1,19 @@
use std::cell::RefCell;
use std::rc::Rc;
use std::{cell::RefCell, rc::Rc};
use apply::Apply;
use cosmic::iced::widget::{checkbox, column, progress_bar, radio, slider, text};
use cosmic::iced::{Alignment, Length};
use cosmic::iced_core::id;
use cosmic::theme::ThemeType;
use cosmic::widget::color_picker::ColorPickerUpdate;
use cosmic::widget::{
button, dropdown, icon, layer_container as container, segmented_button, segmented_control,
settings, spin_button, tab_bar, toggler, ColorPickerModel,
use cosmic::{
cosmic_theme,
iced::widget::{checkbox, column, progress_bar, radio, slider, text},
iced::{Alignment, Length},
iced_core::id,
theme::ThemeType,
widget::{
button, color_picker::ColorPickerUpdate, dropdown, icon, layer_container as container,
segmented_button, segmented_control, settings, spin_button, tab_bar, toggler,
ColorPickerModel,
},
Element,
};
use cosmic::{cosmic_theme, Element};
use cosmic_time::{anim, chain, Timeline};
use fraction::{Decimal, ToPrimitive};
use once_cell::sync::Lazy;

View file

@ -1,7 +1,10 @@
use cosmic::iced::widget::{column, container, horizontal_space, image, row, svg, text};
use cosmic::iced::Length;
use cosmic::widget::{list_column, settings, toggler};
use cosmic::{theme, Element};
use cosmic::{
iced::widget::{column, container, horizontal_space, image, row, svg, text},
iced::Length,
theme,
widget::{list_column, settings, toggler},
Element,
};
use super::{Page, SubPage, Window};

View file

@ -1,7 +1,9 @@
use cosmic::iced::widget::{horizontal_space, row, text};
use cosmic::iced::Length;
use cosmic::widget::{icon, list_column, settings};
use cosmic::Element;
use cosmic::{
iced::widget::{horizontal_space, row, text},
iced::Length,
widget::{icon, list_column, settings},
Element,
};
use super::{Message, Page, SubPage, Window};

View file

@ -9,9 +9,11 @@ use std::{env, process};
use cosmic::app::{Core, Settings, Task};
use cosmic::iced::alignment::{Horizontal, Vertical};
use cosmic::iced::keyboard::Key;
use cosmic::iced::{window, Length, Size};
use cosmic::iced::window;
use cosmic::iced::{Length, Size};
use cosmic::widget::menu::action::MenuAction;
use cosmic::widget::menu::key_bind::{KeyBind, Modifier};
use cosmic::widget::menu::key_bind::KeyBind;
use cosmic::widget::menu::key_bind::Modifier;
use cosmic::widget::menu::{self, ItemHeight, ItemWidth};
use cosmic::widget::RcElementWrapper;
use cosmic::{executor, Element};

View file

@ -1,11 +1,13 @@
use std::collections::HashMap;
use cosmic::app::Core;
use cosmic::iced::core::{id, Alignment, Length, Point};
use cosmic::iced::widget::{column, container, scrollable, text};
use cosmic::iced::{self, event, window, Subscription};
use cosmic::prelude::*;
use cosmic::widget::{button, header_bar};
use cosmic::{
app::Core,
iced::core::{id, Alignment, Length, Point},
iced::widget::{column, container, scrollable, text},
iced::{self, event, window, Subscription},
prelude::*,
widget::{button, header_bar},
};
#[derive(Debug, Clone, PartialEq)]
pub enum Message {

View file

@ -1,8 +1,15 @@
use cosmic::app::{Core, Task};
use cosmic::iced::alignment::{Horizontal, Vertical};
use cosmic::iced::{self, Alignment, Length, Size};
use cosmic::iced::Length;
use cosmic::widget::{column, container, spin_button};
use cosmic::{Application, Apply, Element};
use cosmic::Apply;
use cosmic::{
app::{Core, Task},
iced::{
self,
alignment::{Horizontal, Vertical},
Alignment, Size,
},
Application, Element,
};
use fraction::Decimal;
pub struct SpinButtonExamplApp {

View file

@ -5,8 +5,7 @@
use cosmic::app::{Core, Settings, Task};
use cosmic::iced::Subscription;
use cosmic::prelude::*;
use cosmic::{executor, widget};
use cosmic::{executor, prelude::*, widget};
/// Runs application with these settings
fn main() -> Result<(), Box<dyn std::error::Error>> {

View file

@ -9,7 +9,8 @@ use chrono::Datelike;
use cosmic::app::{Core, Settings, Task};
use cosmic::iced::Size;
use cosmic::prelude::*;
use cosmic::widget::{self, nav_bar, table};
use cosmic::widget::table;
use cosmic::widget::{self, nav_bar};
use cosmic::{executor, iced};
#[derive(Debug, Default, PartialEq, Eq, Clone, Copy, Hash)]

View file

@ -1,34 +0,0 @@
close = Κλείσιμο
february = Φεβρουάριος { $year }
documenters = Τεκμηριωτές
november = Νοέμβριος { $year }
may = Μάιος { $year }
april = Απρίλιος { $year }
translators = Μεταφραστές
artists = Καλλιτέχνες
license = Άδεια χρήσης
december = Δεκέμβριος { $year }
links = Σύνδεσμοι
march = Μάρτιος { $year }
june = Ιούνιος { $year }
august = Αύγουστος { $year }
developers = Προγραμματιστές
july = Ιούλιος { $year }
september = Σεπτέμβριος { $year }
designers = Σχεδιαστές
october = Οκτώβριος { $year }
january = Ιανουάριος { $year }
monday = Δευτέρα
mon = Δευ
tuesday = Τρίτη
tue = Τρί
wednesday = Τετάρτη
wed = Τετ
thursday = Πέμπτη
thu = Πέμ
friday = Παρασκευή
fri = Παρ
saturday = Σάββατο
sat = Σάβ
sunday = Κυριακή
sun = Κυρ

View file

View file

@ -1,34 +0,0 @@
close = Затвори
february = Фебруар { $year }
documenters = Документатори
november = Новембар { $year }
may = Мај { $year }
april = Април { $year }
translators = Преводиоци
artists = Уметници
license = Дозвола
december = Децембар { $year }
links = Везе
march = Март { $year }
june = Јун { $year }
august = Август { $year }
developers = Програмери
july = Јул { $year }
september = Септембар { $year }
designers = Дизајнери
october = Октобар { $year }
january = Јануар { $year }
monday = Понедељак
mon = Пон
tuesday = Уторак
tue = Уто
wednesday = Среда
wed = Сре
thursday = Четвртак
thu = Чет
friday = Петак
fri = Пет
saturday = Субота
sat = Суб
sunday = Недеља
sun = Нед

View file

@ -1 +0,0 @@
imports_granularity = "Module"

View file

@ -1,10 +1,10 @@
// Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
use crate::config::CosmicTk;
use crate::surface;
use crate::theme::Theme;
use crate::widget::nav_bar;
use crate::{keyboard_nav, surface};
use crate::{config::CosmicTk, keyboard_nav};
#[cfg(all(feature = "wayland", target_os = "linux"))]
use cctk::sctk::reexports::csd_frame::{WindowManagerCapabilities, WindowState};
use cosmic_theme::ThemeMode;

View file

@ -22,11 +22,11 @@ use crate::prelude::*;
use crate::theme::THEME;
use crate::widget::{container, id_container, menu, nav_bar, popover, space};
use apply::Apply;
use iced::{Length, Subscription, theme, window};
use iced::{Length, Subscription};
use iced::{theme, window};
pub use settings::Settings;
use std::borrow::Cow;
use std::cell::RefCell;
use std::rc::Rc;
use std::{cell::RefCell, rc::Rc};
#[cold]
pub(crate) fn iced_settings<App: Application>(
@ -773,6 +773,7 @@ impl<App: Application> ApplicationExt for App {
.focused(focused)
.maximized(maximized)
.sharp_corners(sharp_corners)
.transparent(content_container)
.title(&core.window.header_title)
.on_drag(crate::Action::Cosmic(Action::Drag))
.on_right_click(crate::Action::Cosmic(Action::ShowWindowMenu))

View file

@ -1,11 +1,15 @@
//! Distribute content vertically.
use crate::iced;
use iced::core::alignment::{self, Alignment};
use iced::core::event::{self, Event};
use iced::core::event::Event;
use iced::core::layout;
use iced::core::mouse;
use iced::core::overlay;
use iced::core::renderer;
use iced::core::widget::{Operation, Tree};
use iced::core::{
Clipboard, Element, Layout, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget,
layout, mouse, overlay, renderer, widget,
widget,
};
/// A container that distributes its contents vertically.

View file

@ -1,31 +1,33 @@
#[cfg(feature = "applet-token")]
pub mod token;
use crate::app::{BootData, BootDataInner, cosmic, iced_settings};
use crate::cctk::sctk;
use crate::theme::{self, Button, THEME, system_dark, system_light};
use crate::widget::autosize::{self, Autosize, autosize};
use crate::widget::column::Column;
use crate::widget::row::Row;
use crate::widget::space::{horizontal, vertical};
use crate::widget::{self, layer_container};
use crate::{Application, Element, Renderer};
use crate::app::{BootData, BootDataInner, cosmic};
use crate::{
Application, Element, Renderer,
app::iced_settings,
cctk::sctk,
theme::{self, Button, THEME, system_dark, system_light},
widget::{
self,
autosize::{self, Autosize, autosize},
layer_container,
},
};
pub use cosmic_panel_config;
use cosmic_panel_config::{CosmicPanelBackground, PanelAnchor, PanelSize};
use iced::alignment::{Alignment, Horizontal, Vertical};
use iced::widget::Container;
use iced::{self, Color, Length, Limits, Rectangle, window};
use iced::{
self, Color, Length, Limits, Rectangle,
alignment::{Alignment, Horizontal, Vertical},
widget::Container,
window,
};
use iced_core::{Padding, Shadow};
use iced_runtime::platform_specific::wayland::popup::{SctkPopupSettings, SctkPositioner};
use iced_widget::Text;
use sctk::reexports::protocols::xdg::shell::client::xdg_positioner::{Anchor, Gravity};
use std::borrow::Cow;
use std::cell::RefCell;
use std::num::NonZeroU32;
use std::rc::Rc;
use std::sync::LazyLock;
use std::time::Duration;
use std::{borrow::Cow, num::NonZeroU32, rc::Rc, sync::LazyLock, time::Duration};
use tracing::info;
pub mod column;
@ -211,12 +213,6 @@ impl Context {
icon: widget::icon::Handle,
) -> crate::widget::Button<'a, Message> {
let suggested = self.suggested_size(icon.symbolic);
let (applet_padding_major_axis, applet_padding_minor_axis) = self.suggested_padding(true);
let (horizontal_padding, vertical_padding) = if self.is_horizontal() {
(applet_padding_major_axis, applet_padding_minor_axis)
} else {
(applet_padding_minor_axis, applet_padding_major_axis)
};
let symbolic = icon.symbolic;
let icon = widget::icon(icon)
.class(if symbolic {
@ -459,10 +455,8 @@ impl Context {
&self,
content: impl Into<Element<'a, Message>>,
) -> Autosize<'a, Message, crate::Theme, crate::Renderer> {
let force_configured = matches!(&self.panel_type, PanelType::Other(n) if n.is_empty());
let w = autosize(content, AUTOSIZE_MAIN_ID.clone());
let mut limits = Limits::NONE;
let suggested_window_size = self.suggested_window_size();
if let Some(width) = self
.suggested_bounds
@ -573,7 +567,7 @@ pub fn run<App: Application>(flags: App::Flags) -> iced::Result {
// window_settings = window_settings.clone();
core.main_window = Some(iced_core::window::Id::RESERVED);
}
let mut app = iced::daemon(
let app = iced::daemon(
BootData(Rc::new(RefCell::new(Some(BootDataInner::<App> {
flags,
core,

View file

@ -3,10 +3,12 @@ use crate::iced;
use iced::core::alignment::{self, Alignment};
use iced::core::event::Event;
use iced::core::layout::{self, Layout};
use iced::core::mouse;
use iced::core::overlay;
use iced::core::renderer;
use iced::core::widget::{Operation, Tree};
use iced::core::{
Clipboard, Element, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget, mouse,
overlay, renderer, widget,
Clipboard, Element, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget, widget,
};
/// A container that distributes its contents horizontally.

View file

@ -1,12 +1,13 @@
use crate::iced;
use cctk::sctk::reexports::calloop;
use futures::channel::mpsc::{UnboundedReceiver, unbounded};
use futures::{SinkExt, StreamExt};
use futures::{
SinkExt, StreamExt,
channel::mpsc::{UnboundedReceiver, unbounded},
};
use iced::Subscription;
use iced_futures::{futures, stream};
use std::fmt::Debug;
use std::hash::Hash;
use std::thread::JoinHandle;
use iced_futures::futures;
use iced_futures::stream;
use std::{fmt::Debug, hash::Hash, thread::JoinHandle};
use super::wayland_handler::wayland_handler;

View file

@ -1,20 +1,27 @@
use std::os::fd::{FromRawFd, RawFd};
use std::os::unix::net::UnixStream;
use std::os::{
fd::{FromRawFd, RawFd},
unix::net::UnixStream,
};
use super::subscription::{TokenRequest, TokenUpdate};
use cctk::sctk::activation::{RequestData, RequestDataExt};
use cctk::sctk::reexports::calloop;
use cctk::sctk::reexports::calloop_wayland_source::WaylandSource;
use cctk::sctk::seat::{SeatHandler, SeatState};
use cctk::sctk::{self};
use cctk::wayland_client::protocol::wl_seat::WlSeat;
use cctk::wayland_client::protocol::wl_surface::WlSurface;
use cctk::wayland_client::{self};
use cctk::{
sctk::{
self,
activation::{RequestData, RequestDataExt},
reexports::{calloop, calloop_wayland_source::WaylandSource},
seat::{SeatHandler, SeatState},
},
wayland_client::{
self,
protocol::{wl_seat::WlSeat, wl_surface::WlSurface},
},
};
use iced_futures::futures::channel::mpsc::UnboundedSender;
use sctk::activation::{ActivationHandler, ActivationState};
use sctk::registry::{ProvidesRegistryState, RegistryState};
use wayland_client::globals::registry_queue_init;
use wayland_client::{Connection, QueueHandle};
use sctk::{
activation::{ActivationHandler, ActivationState},
registry::{ProvidesRegistryState, RegistryState},
};
use wayland_client::{Connection, QueueHandle, globals::registry_queue_init};
struct AppData {
exit: bool,
@ -164,18 +171,7 @@ pub(crate) fn wayland_handler(
if app_data.exit {
break;
}
if let Err(why) = event_loop.dispatch(None, &mut app_data) {
if let calloop::Error::IoError(ref why) = why
&& why.kind() == std::io::ErrorKind::BrokenPipe
{
tracing::info!("Connection to panel has ended. The applet will now exit with it.");
break;
}
tracing::error!(?why, "dispatch error on Wayland connection to panel");
break;
}
event_loop.dispatch(None, &mut app_data).unwrap();
}
}

View file

@ -1,15 +1,17 @@
// Copyright 2024 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
use crate::ApplicationExt;
use iced::Subscription;
use iced_futures::futures::SinkExt;
use iced_futures::futures::channel::mpsc::{Receiver, Sender};
use std::any::TypeId;
use std::collections::HashMap;
use url::Url;
use zbus::zvariant::Value;
use zbus::{interface, proxy};
use {
crate::ApplicationExt,
iced::Subscription,
iced_futures::futures::{
SinkExt,
channel::mpsc::{Receiver, Sender},
},
std::{any::TypeId, collections::HashMap},
url::Url,
zbus::{interface, proxy, zvariant::Value},
};
#[cold]
pub fn subscription<App: ApplicationExt>() -> Subscription<crate::Action<App::Message>> {

View file

@ -876,8 +876,7 @@ trait SystemdManger {
#[cfg(all(test, not(windows)))]
mod tests {
use super::*;
use std::path::{Path, PathBuf};
use std::{env, fs};
use std::{env, fs, path::Path, path::PathBuf};
use tempfile::tempdir;
struct EnvVarGuard {

View file

@ -3,7 +3,7 @@
#![allow(clippy::module_name_repetitions)]
#![cfg_attr(target_os = "redox", feature(lazy_cell))]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
/// Recommended default imports.
pub mod prelude {

View file

@ -1,7 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-only
use i18n_embed::fluent::{FluentLanguageLoader, fluent_language_loader};
use i18n_embed::{DefaultLocalizer, LanguageLoader, Localizer};
use i18n_embed::{
DefaultLocalizer, LanguageLoader, Localizer,
fluent::{FluentLanguageLoader, fluent_language_loader},
};
use rust_embed::RustEmbed;
use std::sync::{LazyLock, OnceLock};

View file

@ -5,7 +5,7 @@
use smol::io::AsyncReadExt;
use std::io;
use std::os::fd::OwnedFd;
use std::process::{Command, Stdio};
use std::process::{Command, Stdio, exit};
#[cfg(feature = "tokio")]
use tokio::io::AsyncReadExt;
@ -56,33 +56,24 @@ pub async fn spawn(mut command: Command) -> Option<u32> {
match unsafe { libc::fork() } {
// Parent process
fork_pid @ 1..=i32::MAX => {
1.. => {
// Drop copy of write end, then read PID from pipe
drop(write);
let pid = read_from_pipe(read).await;
_ = rustix::process::waitpid(
rustix::process::Pid::from_raw(fork_pid),
rustix::process::WaitOptions::empty(),
);
// wait to prevent zombie
_ = rustix::process::wait(rustix::process::WaitOptions::empty());
pid
}
// Child process
0 => {
let _res = rustix::process::setsid();
let exit_status = if let Ok(child) = command.spawn() {
if let Ok(child) = command.spawn() {
// Write PID to pipe
let _ = rustix::io::write(write, &child.id().to_be_bytes());
0
} else {
1
};
// # Safety
// Required for child fork to exit without affecting the parent.
unsafe {
libc::_exit(exit_status);
}
exit(0)
}
..=-1 => {

View file

@ -5,8 +5,7 @@ use super::Action;
use crate::Application;
use iced::window;
use std::any::Any;
use std::sync::Arc;
use std::{any::Any, sync::Arc};
/// Used to produce a destroy popup message from within a widget.
#[cfg(all(feature = "wayland", target_os = "linux"))]

View file

@ -3,8 +3,9 @@
pub mod action;
use iced::{Limits, Size, Task};
use std::future::Future;
use iced::Limits;
use iced::Size;
use iced::Task;
use std::sync::Arc;
/// Ignore this message in your application. It will be intercepted.

View file

@ -7,9 +7,11 @@
pub mod portal;
pub mod style;
use cosmic_config::{CosmicConfigEntry, config_subscription};
use cosmic_theme::{Component, LayeredTheme, Spacing, ThemeMode};
use iced_futures::Subscription;
use cosmic_config::CosmicConfigEntry;
use cosmic_theme::Component;
use cosmic_theme::LayeredTheme;
use cosmic_theme::Spacing;
use cosmic_theme::ThemeMode;
use iced_runtime::{Appearance, DefaultStyle};
use std::sync::{Arc, LazyLock, Mutex};
pub use style::*;

View file

@ -6,8 +6,10 @@
use cosmic_theme::Component;
use iced_core::{Background, Color};
use crate::theme::TRANSPARENT_COMPONENT;
use crate::widget::button::{Catalog, Style};
use crate::{
theme::TRANSPARENT_COMPONENT,
widget::button::{Catalog, Style},
};
#[derive(Default)]
pub enum Button {
@ -148,7 +150,7 @@ pub fn appearance(
}
Button::ListItem(radii) => {
corner_radii = radii;
let (background, text, icon) = color(&cosmic.list_button);
let (background, text, icon) = color(&cosmic.background.component);
if selected {
appearance.background =
@ -195,7 +197,7 @@ impl Catalog for crate::Theme {
return active(focused, self);
}
appearance(self, focused, selected, false, style, move |component| {
let mut s = appearance(self, focused, selected, false, style, move |component| {
let text_color = if matches!(
style,
Button::Icon | Button::IconVertical | Button::HeaderBar
@ -207,7 +209,14 @@ impl Catalog for crate::Theme {
};
(component.base.into(), text_color, text_color)
})
});
if let Button::ListItem(_) = style
&& !selected {
s.background = None;
}
s
}
fn disabled(&self, style: &Self::Class) -> Style {
@ -235,7 +244,7 @@ impl Catalog for crate::Theme {
return hovered(focused, self);
}
appearance(
let mut s = appearance(
self,
focused || matches!(style, Button::Image),
selected,
@ -254,7 +263,14 @@ impl Catalog for crate::Theme {
(component.hover.into(), text_color, text_color)
},
)
);
if let Button::ListItem(_) = style
&& !selected {
s.background = None;
}
s
}
fn pressed(&self, focused: bool, selected: bool, style: &Self::Class) -> Style {

View file

@ -5,17 +5,18 @@
use crate::theme::{CosmicComponent, TRANSPARENT_COMPONENT, Theme};
use cosmic_theme::composite::over;
use iced::overlay::menu;
use iced::theme::Base;
use iced::widget::slider::{self, Rail};
use iced::widget::{
button as iced_button, checkbox as iced_checkbox, combo_box, container as iced_container,
pane_grid, pick_list, progress_bar, radio, rule, scrollable, svg, toggler,
use iced::{
overlay::menu,
theme::Base,
widget::{
button as iced_button, checkbox as iced_checkbox, combo_box, container as iced_container,
pane_grid, pick_list, progress_bar, radio, rule, scrollable,
slider::{self, Rail},
svg, toggler,
},
};
use iced_core::{Background, Border, Color, Shadow, Vector};
use iced_widget::pane_grid::Highlight;
use iced_widget::scrollable::AutoScroll;
use iced_widget::{text_editor, text_input};
use iced_widget::{pane_grid::Highlight, scrollable::AutoScroll, text_editor, text_input};
use palette::WithAlpha;
use std::rc::Rc;
@ -1338,16 +1339,9 @@ impl iced_widget::text::Catalog for Theme {
match class {
Text::Accent => iced_widget::text::Style {
color: Some(self.cosmic().accent_text_color().into()),
..Default::default()
},
Text::Default => iced_widget::text::Style {
color: None,
..Default::default()
},
Text::Color(c) => iced_widget::text::Style {
color: Some(*c),
..Default::default()
},
Text::Default => iced_widget::text::Style { color: None },
Text::Color(c) => iced_widget::text::Style { color: Some(*c) },
Text::Custom(f) => f(self),
}
}

View file

@ -71,7 +71,7 @@ impl StyleSheet for Theme {
background: component.base.into(),
border_width: 1.0,
bar_border_radius: cosmic.corner_radii.radius_xl,
menu_border_radius: cosmic.corner_radii.radius_s,
menu_border_radius: cosmic.corner_radii.radius_s.map(|x| x + 2.0),
border_color: component.divider.into(),
background_expand: [1; 4],
path: component.hover.into(),

View file

@ -3,13 +3,10 @@
//! Contains stylesheet implementation for [`crate::widget::segmented_button`].
use crate::theme::Theme;
use crate::widget::segmented_button::{
Appearance, ItemAppearance, ItemStatusAppearance, StyleSheet,
};
use crate::widget::segmented_button::{Appearance, ItemAppearance, StyleSheet};
use crate::{theme::Theme, widget::segmented_button::ItemStatusAppearance};
use iced::Border;
use iced_core::Background;
use iced_core::border::Radius;
use iced_core::{Background, border::Radius};
use palette::WithAlpha;
#[derive(Default)]
@ -146,8 +143,7 @@ mod horizontal {
use crate::widget::segmented_button::{ItemAppearance, ItemStatusAppearance};
use cosmic_theme::{Component, Container};
use iced::Border;
use iced_core::Background;
use iced_core::border::Radius;
use iced_core::{Background, border::Radius};
use palette::WithAlpha;
pub fn tab_bar(cosmic: &cosmic_theme::Theme, container: &Container) -> Appearance {
@ -254,8 +250,7 @@ mod vertical {
use crate::widget::segmented_button::{ItemAppearance, ItemStatusAppearance};
use cosmic_theme::{Component, Container};
use iced::Border;
use iced_core::Background;
use iced_core::border::Radius;
use iced_core::{Background, border::Radius};
use palette::WithAlpha;
pub fn tab_bar(cosmic: &cosmic_theme::Theme, container: &Container) -> Appearance {

View file

@ -1,6 +1,8 @@
use crate::iced::{Alignment, Length};
use crate::widget::{self, list};
use crate::{Apply, Element, fl};
use crate::{
Apply, Element, fl,
iced::{Alignment, Length},
widget::{self, list},
};
use std::rc::Rc;
#[derive(Debug, Default, Clone, derive_setters::Setters)]

View file

@ -3,10 +3,13 @@
use iced::Size;
use iced::widget::Container;
use iced_core::event::Event;
use iced_core::layout;
use iced_core::mouse;
use iced_core::overlay;
use iced_core::renderer;
use iced_core::widget::Tree;
use iced_core::{
Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Vector, Widget,
layout, mouse, overlay, renderer,
};
pub use iced_widget::container::{Catalog, Style};

View file

@ -1,11 +1,12 @@
//! Autosize Container, which will resize the window to its contents.
use iced_core::event::{self, Event};
use iced_core::layout;
use iced_core::mouse;
use iced_core::overlay;
use iced_core::renderer;
use iced_core::widget::{Id, Operation, Tree};
use iced_core::{
Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget, layout, mouse, overlay,
renderer,
};
use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget};
pub use iced_widget::container::{Catalog, Style};
pub fn autosize<'a, Message: 'static, Theme, E>(

View file

@ -3,13 +3,9 @@
use super::{Builder, ButtonClass};
use crate::Element;
use crate::widget::icon::Handle;
use crate::widget::tooltip;
use crate::widget::{icon::Handle, tooltip};
use apply::Apply;
use iced_core::font::Weight;
use iced_core::text::LineHeight;
use iced_core::widget::Id;
use iced_core::{Alignment, Length, Padding};
use iced_core::{Alignment, Length, Padding, font::Weight, text::LineHeight, widget::Id};
use std::borrow::Cow;
pub type Button<'a, Message> = Builder<'a, Message, Icon>;

View file

@ -2,12 +2,11 @@
// SPDX-License-Identifier: MPL-2.0
use super::Builder;
use crate::Element;
use crate::widget::image::Handle;
use crate::widget::{self};
use iced_core::font::Weight;
use iced_core::widget::Id;
use iced_core::{Length, Padding};
use crate::{
Element,
widget::{self, image::Handle},
};
use iced_core::{Length, Padding, font::Weight, widget::Id};
use std::borrow::Cow;
pub type Button<'a, Message> = Builder<'a, Message, Image<'a, Handle, Message>>;

View file

@ -3,15 +3,14 @@
//! Hyperlink button widget
use super::{Builder, ButtonClass};
use super::Builder;
use super::ButtonClass;
use crate::Element;
use crate::prelude::*;
use crate::widget::icon::{self, Handle};
use crate::widget::{button, row, tooltip};
use iced_core::font::Weight;
use iced_core::text::LineHeight;
use iced_core::widget::Id;
use iced_core::{Alignment, Length, Padding};
use iced_core::{Alignment, Length, Padding, font::Weight, widget::Id};
use std::borrow::Cow;
pub type Button<'a, Message> = Builder<'a, Message, Hyperlink>;

View file

@ -2,8 +2,7 @@
// SPDX-License-Identifier: MPL-2.0
//! Change the apperance of a button.
use iced_core::border::Radius;
use iced_core::{Background, Color, Vector};
use iced_core::{Background, Color, Vector, border::Radius};
use crate::theme::THEME;

View file

@ -4,10 +4,7 @@
use super::{Builder, ButtonClass};
use crate::widget::{icon, row, tooltip};
use crate::{Apply, Element};
use iced_core::font::Weight;
use iced_core::text::LineHeight;
use iced_core::widget::Id;
use iced_core::{Alignment, Length, Padding};
use iced_core::{Alignment, Length, Padding, font::Weight, text::LineHeight, widget::Id};
use std::borrow::Cow;
pub type Button<'a, Message> = Builder<'a, Message, Text>;

View file

@ -11,12 +11,15 @@ use iced_runtime::{Action, Task, keyboard, task};
use iced_core::event::Event;
use iced_core::renderer::{self, Quad, Renderer};
use iced_core::touch;
use iced_core::widget::Operation;
use iced_core::widget::tree::{self, Tree};
use iced_core::{
Background, Border, Clipboard, Color, Layout, Length, Padding, Point, Rectangle, Shadow, Shell,
Vector, Widget, layout, mouse, overlay, svg, touch,
Background, Clipboard, Color, Layout, Length, Padding, Point, Rectangle, Shell, Vector, Widget,
};
use iced_core::{Border, mouse};
use iced_core::{Shadow, overlay};
use iced_core::{layout, svg};
use iced_renderer::core::widget::operation;
use crate::theme::THEME;
@ -641,8 +644,10 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
state: &Tree,
p: mouse::Cursor,
) -> iced_accessibility::A11yTree {
use iced_accessibility::accesskit::{Action, Node, NodeId, Rect, Role};
use iced_accessibility::{A11yNode, A11yTree};
use iced_accessibility::{
A11yNode, A11yTree,
accesskit::{Action, Node, NodeId, Rect, Role},
};
// TODO why is state None sometimes?
if matches!(state.state, iced_core::widget::tree::State::None) {
tracing::info!("Button state is missing.");

View file

@ -8,8 +8,10 @@ use crate::widget::{button, column, grid, icon, row, text};
use apply::Apply;
use iced::alignment::Vertical;
use iced_core::{Alignment, Length};
use jiff::ToSpan;
use jiff::civil::{Date, Weekday};
use jiff::{
ToSpan,
civil::{Date, Weekday},
};
/// A widget that displays an interactive calendar.
pub fn calendar<M>(

View file

@ -1,18 +1,23 @@
//! An expandable stack of cards
use std::time::Duration;
use crate::anim;
use crate::widget::card::style::Style;
use crate::widget::icon::{self, Handle};
use crate::widget::{button, column, row, text};
use crate::{
anim,
widget::{
button,
card::style::Style,
column,
icon::{self, Handle},
row, text,
},
};
use float_cmp::approx_eq;
use iced::widget;
use iced_core::border::Radius;
use iced_core::id::Id;
use iced_core::layout::Node;
use iced_core::renderer::Quad;
use iced_core::widget::{Tree, tree};
use iced_core::{Border, Element, Event, Length, Shadow, Size, Vector, Widget, window};
use iced_core::{
Border, Element, Event, Length, Shadow, Size, Vector, Widget, border::Radius, id::Id,
layout::Node, renderer::Quad, widget::Tree,
};
use iced_core::{widget::tree, window};
const ICON_SIZE: u16 = 16;
const TOP_SPACING: u16 = 4;

View file

@ -12,9 +12,7 @@ use std::time::{Duration, Instant};
use crate::Element;
use crate::theme::iced::Slider;
use crate::theme::{Button, THEME};
use crate::widget::button::Catalog;
use crate::widget::segmented_button::Entity;
use crate::widget::{container, slider};
use crate::widget::{button::Catalog, container, segmented_button::Entity, slider};
use derive_setters::Setters;
use iced::Task;
use iced_core::event::Event;
@ -27,8 +25,10 @@ use iced_core::{
};
use iced_widget::slider::HandleShape;
use iced_widget::space::{horizontal, vertical};
use iced_widget::{Row, canvas, column, row, scrollable};
use iced_widget::{
Row, canvas, column, row, scrollable,
space::{horizontal, vertical},
};
use palette::{FromColor, RgbHue};
use super::divider::horizontal;

View file

@ -5,7 +5,8 @@ use crate::Element;
use iced::advanced::layout::{self, Layout};
use iced::advanced::widget::{self, Operation};
use iced::advanced::{Clipboard, Shell, overlay, renderer};
use iced::advanced::{Clipboard, Shell};
use iced::advanced::{overlay, renderer};
use iced::{Event, Point, Size, mouse};
use iced_core::{Renderer, touch};

View file

@ -6,10 +6,11 @@ use crate::widget::{self, LayerContainer, button, column, container, icon, row,
use crate::{Apply, Element, Renderer, Theme, fl};
use std::borrow::Cow;
use iced_core::Alignment;
use iced_core::event::Event;
use iced_core::widget::{Operation, Tree};
use iced_core::{
Alignment, Clipboard, Layout, Length, Rectangle, Shell, Vector, Widget, layout, mouse,
Clipboard, Layout, Length, Rectangle, Shell, Vector, Widget, layout, mouse,
overlay as iced_overlay, renderer,
};

View file

@ -79,8 +79,7 @@ impl<Message: Clone + 'static> ContextMenu<'_, Message> {
my_state: &mut LocalState,
) {
if self.window_id != window::Id::NONE && self.on_surface_action.is_some() {
use crate::surface::action::destroy_popup;
use crate::widget::menu::Menu;
use crate::{surface::action::destroy_popup, widget::menu::Menu};
use iced_runtime::platform_specific::wayland::popup::{
SctkPopupSettings, SctkPositioner,
};

View file

@ -1,5 +1,8 @@
use crate::iced::{Length, Pixels};
use crate::{Element, style, theme, widget};
use crate::{
Element,
iced::{Length, Pixels},
style, theme, widget,
};
use std::borrow::Cow;
pub fn dialog<'a, Message>() -> Dialog<'a, Message> {

View file

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

View file

@ -1,14 +1,19 @@
use std::any::Any;
use iced_core::widget::Operation;
use iced_core::window;
use iced_core::{widget::Operation, window};
use crate::Element;
use crate::widget::{Id, Widget, container};
use iced::clipboard::dnd::{DndAction, DndEvent, SourceEvent};
use iced::{Event, Length, Point, Rectangle, Vector, event, mouse, overlay};
use iced_core::widget::{Tree, tree};
use iced_core::{self, Clipboard, Shell, layout, renderer};
use crate::{
Element,
widget::{Id, Widget, container},
};
use iced::{
Event, Length, Point, Rectangle, Vector,
clipboard::dnd::{DndAction, DndEvent, SourceEvent}, mouse, overlay,
};
use iced_core::{
self, Clipboard, Shell, layout, renderer,
widget::{Tree, tree},
};
pub fn dnd_source<
'a,

View file

@ -3,8 +3,7 @@
// SPDX-License-Identifier: MPL-2.0 AND MIT
//! Change the appearance of menus.
use iced_core::border::Radius;
use iced_core::{Background, Color};
use iced_core::{Background, Color, border::Radius};
/// The appearance of a menu.
#[derive(Debug, Clone, Copy)]

View file

@ -9,9 +9,9 @@ use iced_core::event::Event;
use iced_core::text::{self, Text};
use iced_core::widget::tree::{self, Tree};
use iced_core::{
Clipboard, Layout, Length, Padding, Pixels, Rectangle, Shadow, Shell, Size, Vector, Widget,
alignment, keyboard, layout, mouse, overlay, renderer, svg, touch,
Clipboard, Layout, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget,
};
use iced_core::{Shadow, alignment, keyboard, layout, mouse, overlay, renderer, svg, touch};
use iced_widget::pick_list;
pub use iced_widget::pick_list::{Catalog, Style};

View file

@ -12,9 +12,9 @@ use iced_core::event::Event;
use iced_core::text::{self, Paragraph, Text};
use iced_core::widget::tree::{self, Tree};
use iced_core::{
Clipboard, Layout, Length, Padding, Pixels, Rectangle, Shadow, Shell, Size, Vector, Widget,
alignment, keyboard, layout, mouse, overlay, renderer, svg, touch,
Clipboard, Layout, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget,
};
use iced_core::{Shadow, alignment, keyboard, layout, mouse, overlay, renderer, svg, touch};
use iced_widget::pick_list::{self, Catalog};
use std::borrow::Cow;
use std::hash::{DefaultHasher, Hash, Hasher};

View file

@ -2,12 +2,14 @@
//! Based on <https://github.com/tarkah/iced_gif/>
use std::ffi::OsStr;
use std::fmt;
use std::io;
use std::path::Path;
use std::time::{Duration, Instant};
use std::{fmt, io};
use ::image as image_rs;
use iced::{Task, mouse};
use iced::Task;
use iced::mouse;
use iced_core::image::Renderer as ImageRenderer;
use iced_core::mouse::Cursor;
use iced_core::widget::{Tree, tree};

View file

@ -5,8 +5,7 @@ use crate::cosmic_theme::{Density, Spacing};
use crate::{Element, theme, widget};
use apply::Apply;
use derive_setters::Setters;
use iced_core::widget::tree;
use iced_core::{Length, Size, Vector, Widget, layout, text};
use iced_core::{Length, Size, Vector, Widget, layout, text, widget::tree};
use std::borrow::Cow;
#[must_use]
@ -401,7 +400,6 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
space_xxs,
..
} = theme::spacing();
let is_ssd = self.is_ssd;
// Take ownership of the regions to be packed.
let mut start = std::mem::take(&mut self.start);
@ -419,7 +417,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
WindowControlsPosition::Start => start.insert(0, controls),
}
let padding = if is_ssd {
let padding = if self.is_ssd {
[2, 8, 2, 8]
} else {
match (
@ -464,7 +462,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
.class(theme::Container::HeaderBar {
focused: self.focused,
sharp_corners: self.sharp_corners,
transparent: if is_ssd { false } else { true },
transparent: self.transparent,
})
.height(Length::Fixed(32.0 + padding[0] as f32 + padding[2] as f32))
.padding(padding)

View file

@ -2,10 +2,7 @@
// SPDX-License-Identifier: MPL-2.0
use super::{Handle, Icon};
use std::borrow::Cow;
use std::ffi::OsStr;
use std::path::PathBuf;
use std::sync::Arc;
use std::{borrow::Cow, ffi::OsStr, path::PathBuf, sync::Arc};
#[derive(Debug, Clone, Default, Hash)]
/// Fallback icon to use if the icon was not found.
@ -39,10 +36,6 @@ pub struct Named {
/// Prioritizes SVG over PNG
pub prefer_svg: bool,
/// Extra directories to search as flat paths before the icon theme chain.
#[setters(skip)]
pub extra_paths: Vec<PathBuf>,
}
impl Named {
@ -56,30 +49,19 @@ impl Named {
size: None,
scale: None,
prefer_svg: symbolic,
extra_paths: Vec::new(),
}
}
pub fn with_extra_paths(mut self, paths: Vec<PathBuf>) -> Self {
self.extra_paths = paths;
self
}
#[cfg(all(unix, not(target_os = "macos")))]
#[must_use]
pub fn path(self) -> Option<PathBuf> {
let name = &*self.name;
let fallback = &self.fallback;
let extra_paths = &self.extra_paths;
let locate = |theme: &str, name| {
let mut lookup = freedesktop_icons::lookup(name)
.with_theme(theme.as_ref())
.with_cache();
if !extra_paths.is_empty() {
lookup = lookup.with_extra_paths(extra_paths);
}
if let Some(scale) = self.scale {
lookup = lookup.with_scale(scale);
}

View file

@ -1,9 +1,10 @@
use iced_core::event::{self, Event};
use iced_core::event::Event;
use iced_core::layout;
use iced_core::mouse;
use iced_core::overlay;
use iced_core::renderer;
use iced_core::widget::{Id, Operation, Tree};
use iced_core::{
Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget, layout, mouse, overlay,
renderer,
};
use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget};
pub use iced_widget::container::{Catalog, Style};
pub fn id_container<'a, Message: 'static, Theme, E>(

View file

@ -1,11 +1,14 @@
use crate::Theme;
use cosmic_theme::LayeredTheme;
use iced::widget::Container;
use iced_core::event::{self, Event};
use iced_core::event::Event;
use iced_core::layout;
use iced_core::mouse;
use iced_core::overlay;
use iced_core::renderer;
use iced_core::widget::Tree;
use iced_core::{
Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Vector, Widget,
layout, mouse, overlay, renderer,
};
pub use iced_widget::container::{Catalog, Style};

View file

@ -2,8 +2,7 @@
// SPDX-License-Identifier: MPL-2.0
use crate::widget::container::Catalog;
use crate::widget::space::vertical;
use crate::widget::{button, column, container, divider, row};
use crate::widget::{button, column, container, divider, row, space::vertical};
use crate::{Apply, Element, theme};
use iced::{Length, Padding};

View file

@ -1,9 +1,11 @@
// From iced_aw, license MIT
use iced_core::Widget;
use iced_core::widget::Tree;
use iced_widget::core::layout::{Limits, Node};
use iced_widget::core::{Alignment, Element, Padding, Point, Size, renderer};
use iced_core::{Widget, widget::Tree};
use iced_widget::core::{
Alignment, Element, Padding, Point, Size,
layout::{Limits, Node},
renderer,
};
use crate::widget::RcElementWrapper;

View file

@ -1,4 +1,3 @@
use iced_core::keyboard::key::{Code, Physical};
use iced_core::keyboard::{Key, Modifiers};
use std::fmt;
@ -28,102 +27,28 @@ pub struct KeyBind {
}
impl KeyBind {
/// Checks if the given key and modifiers match the `KeyBind`, with an
/// optional fallback to the physical key position for non-Latin keyboard
/// layouts.
/// Checks if the given key and modifiers match the `KeyBind`.
///
/// # Arguments
///
/// * `modifiers` - A `Modifiers` instance representing the current active modifiers.
/// * `key` - A reference to the `Key` that is being checked.
/// * `physical_key` - An optional reference to the physical key position,
/// used as a fallback when the logical `key` does not match (e.g. on
/// Cyrillic or other non-Latin layouts). Can be `None` for keys where
/// the physical position is not relevant (e.g. `Key::Named`).
///
/// # Returns
///
/// * `bool` - `true` if the key and modifiers match the `KeyBind`, `false` otherwise.
pub fn matches(
&self,
modifiers: Modifiers,
key: &Key,
physical_key: Option<&Physical>,
) -> bool {
let key_eq = self.key_eq(key)
|| physical_key
.and_then(physical_key_to_latin)
.is_some_and(|latin| self.key_eq(&latin));
pub fn matches(&self, modifiers: Modifiers, key: &Key) -> bool {
let key_eq = match (key, &self.key) {
// CapsLock and Shift change the case of Key::Character, so we compare these in a case insensitive way
(Key::Character(a), Key::Character(b)) => a.eq_ignore_ascii_case(b),
(a, b) => a.eq(b),
};
key_eq
&& modifiers.logo() == self.modifiers.contains(&Modifier::Super)
&& modifiers.control() == self.modifiers.contains(&Modifier::Ctrl)
&& modifiers.alt() == self.modifiers.contains(&Modifier::Alt)
&& modifiers.shift() == self.modifiers.contains(&Modifier::Shift)
}
fn key_eq(&self, key: &Key) -> bool {
match (key, &self.key) {
// CapsLock and Shift change the case of Key::Character, so we compare these in a case insensitive way
(Key::Character(a), Key::Character(b)) => a.eq_ignore_ascii_case(b),
(a, b) => a.eq(b),
}
}
}
/// Converts a physical key code to the corresponding US-layout Latin `Key`.
///
/// This mapping is intentionally limited to keys that may produce different
/// characters on non-Latin keyboard layouts (letters and punctuation). Keys
/// like digits are not included because they remain the same across layouts.
///
/// Only used as a fallback when the primary key comparison in
/// [`KeyBind::matches`] does not match.
fn physical_key_to_latin(physical_key: &Physical) -> Option<Key> {
let code = match physical_key {
Physical::Code(code) => code,
Physical::Unidentified(_) => return None,
};
let ch = match code {
Code::KeyA => "a",
Code::KeyB => "b",
Code::KeyC => "c",
Code::KeyD => "d",
Code::KeyE => "e",
Code::KeyF => "f",
Code::KeyG => "g",
Code::KeyH => "h",
Code::KeyI => "i",
Code::KeyJ => "j",
Code::KeyK => "k",
Code::KeyL => "l",
Code::KeyM => "m",
Code::KeyN => "n",
Code::KeyO => "o",
Code::KeyP => "p",
Code::KeyQ => "q",
Code::KeyR => "r",
Code::KeyS => "s",
Code::KeyT => "t",
Code::KeyU => "u",
Code::KeyV => "v",
Code::KeyW => "w",
Code::KeyX => "x",
Code::KeyY => "y",
Code::KeyZ => "z",
Code::Minus => "-",
Code::Equal => "=",
Code::BracketLeft => "[",
Code::BracketRight => "]",
Code::Backslash => "\\",
Code::Semicolon => ";",
Code::Quote => "'",
Code::Backquote => "`",
Code::Comma => ",",
Code::Period => ".",
Code::Slash => "/",
_ => return None,
};
Some(Key::Character(ch.into()))
}
impl fmt::Display for KeyBind {

View file

@ -1,14 +1,14 @@
// From iced_aw, license MIT
//! A widget that handles menu trees
use std::collections::HashMap;
use std::sync::Arc;
use std::{collections::HashMap, sync::Arc};
use super::menu_inner::{
CloseCondition, Direction, ItemHeight, ItemWidth, Menu, MenuState, PathHighlight,
use super::{
menu_inner::{
CloseCondition, Direction, ItemHeight, ItemWidth, Menu, MenuState, PathHighlight,
},
menu_tree::MenuTree,
};
use super::menu_tree::MenuTree;
use crate::Renderer;
#[cfg(all(
feature = "multi-window",
feature = "wayland",
@ -16,21 +16,25 @@ use crate::Renderer;
feature = "surface-message"
))]
use crate::app::cosmic::{WINDOWING_SYSTEM, WindowingSystem};
use crate::style::menu_bar::StyleSheet;
use crate::widget::RcWrapper;
use crate::widget::dropdown::menu::{self, State};
use crate::widget::menu::menu_inner::init_root_menu;
use crate::{
Renderer,
style::menu_bar::StyleSheet,
widget::{
RcWrapper,
menu::menu_inner::init_root_menu,
},
};
use iced::event::Status;
use iced::{Point, Shadow, Vector, window};
use iced_core::Border;
use iced_widget::core::layout::{Limits, Node};
use iced_widget::core::mouse::{self, Cursor};
use iced_widget::core::renderer::{self, Renderer as IcedRenderer};
use iced_widget::core::widget::{Tree, tree};
use iced_widget::core::{
Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget, event,
overlay, touch,
layout::{Limits, Node},
mouse::{self, Cursor},
overlay,
renderer::{self, Renderer as IcedRenderer},
touch,
widget::{Tree, tree},
};
/// A `MenuBar` collects `MenuTree`s and handles all the layout, event processing, and drawing.
@ -570,8 +574,7 @@ where
viewport: &Rectangle,
) {
use event::Event::{Mouse, Touch};
use mouse::Button::Left;
use mouse::Event::ButtonReleased;
use mouse::{Button::Left, Event::ButtonReleased};
use touch::Event::{FingerLifted, FingerLost};
process_root_events(
@ -738,7 +741,7 @@ where
tree: &'b mut Tree,
layout: Layout<'b>,
_renderer: &Renderer,
viewport: &Rectangle,
_viewport: &Rectangle,
translation: Vector,
) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
#[cfg(all(

View file

@ -1,11 +1,9 @@
// From iced_aw, license MIT
//! Menu tree overlay
use std::borrow::Cow;
use std::sync::Arc;
use std::{borrow::Cow, sync::Arc};
use super::menu_bar::MenuBarState;
use super::menu_tree::MenuTree;
use super::{menu_bar::MenuBarState, menu_tree::MenuTree};
#[cfg(all(
feature = "multi-window",
feature = "wayland",
@ -17,12 +15,12 @@ use crate::style::menu_bar::StyleSheet;
use iced::window;
use iced_core::{Border, Renderer as IcedRenderer, Shadow, Widget};
use iced_widget::core::layout::{Limits, Node};
use iced_widget::core::mouse::{self, Cursor};
use iced_widget::core::widget::Tree;
use iced_widget::core::{
Clipboard, Layout, Length, Padding, Point, Rectangle, Shell, Size, Vector, event, overlay,
renderer, touch,
Clipboard, Layout, Length, Padding, Point, Rectangle, Shell, Size, Vector, event,
layout::{Limits, Node},
mouse::{self, Cursor},
overlay, renderer, touch,
widget::Tree,
};
/// The condition of when to close a menu
@ -561,9 +559,10 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
shell: &mut Shell<'_, Message>,
) -> Option<(usize, MenuState)> {
use event::Event::{Mouse, Touch};
use event::Status::{Captured, Ignored};
use mouse::Button::Left;
use mouse::Event::{ButtonPressed, ButtonReleased, CursorMoved, WheelScrolled};
use mouse::{
Button::Left,
Event::{ButtonPressed, ButtonReleased, CursorMoved, WheelScrolled},
};
use touch::Event::{FingerLifted, FingerMoved, FingerPressed};
if !self
@ -579,7 +578,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
let viewport_size = viewport.size();
let overlay_offset = Point::ORIGIN - viewport.position();
let overlay_cursor = view_cursor.position().unwrap_or_default() - overlay_offset;
let menu_roots = match &mut self.menu_roots {
let _menu_roots = match &mut self.menu_roots {
Cow::Borrowed(_) => panic!(),
Cow::Owned(o) => o.as_mut_slice(),
};
@ -949,7 +948,7 @@ impl<Message: std::clone::Clone + 'static> Widget<Message, crate::Theme, crate::
#[allow(clippy::too_many_lines)]
fn update(
&mut self,
tree: &mut Tree,
_tree: &mut Tree,
event: &iced::Event,
layout: Layout<'_>,
cursor: mouse::Cursor,
@ -967,21 +966,21 @@ impl<Message: std::clone::Clone + 'static> Widget<Message, crate::Theme, crate::
target_os = "linux"
))]
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland))
&& let Some((new_root, new_ms)) = new_root
&& let Some((new_root, _new_ms)) = new_root
{
use iced_runtime::platform_specific::wayland::popup::{
SctkPopupSettings, SctkPositioner,
};
let overlay_offset = Point::ORIGIN - viewport.position();
let overlay_cursor = cursor.position().unwrap_or_default() - overlay_offset;
let _overlay_cursor = cursor.position().unwrap_or_default() - overlay_offset;
let Some((mut menu, popup_id)) = self.tree.inner.with_data_mut(|state| {
let popup_id = *state
.popup_id
.entry(self.window_id)
.or_insert_with(window::Id::unique);
let active_roots = state
let _active_roots = state
.active_root
.get(self.depth)
.cloned()
@ -995,7 +994,7 @@ impl<Message: std::clone::Clone + 'static> Widget<Message, crate::Theme, crate::
.map(|lo| lo.bounds())
.collect();
let mut popup_menu = Menu {
let popup_menu = Menu {
tree: self.tree.clone(),
menu_roots: Cow::Owned(Cow::into_owned(self.menu_roots.clone())),
bounds_expand: self.bounds_expand,
@ -1600,7 +1599,7 @@ fn process_scroll_events<Message>(
) where
Message: Clone,
{
use event::Status::{Captured, Ignored};
use mouse::ScrollDelta;
menu.tree.inner.with_data_mut(|state| {

View file

@ -234,7 +234,6 @@ pub fn menu_items<
color.alpha *= 0.75;
TextStyle {
color: Some(color.into()),
..Default::default()
}
}
let key_class = theme::Text::Custom(key_style);

View file

@ -152,7 +152,7 @@ pub use dialog::{Dialog, dialog};
pub mod divider {
/// Horizontal variant of a divider.
pub mod horizontal {
use iced::widget::{Rule, rule};
use iced::{widget::Rule, widget::rule};
/// Horizontal divider with default thickness
#[must_use]

View file

@ -6,9 +6,10 @@
//! For details on the model, see the [`segmented_button`] module for more details.
use apply::Apply;
use iced::clipboard::dnd::DndAction;
use iced::clipboard::mime::AllowedMimeTypes;
use iced::{Background, Length};
use iced::{
Background, Length,
clipboard::{dnd::DndAction, mime::AllowedMimeTypes},
};
use iced_core::{Border, Color, Shadow};
use crate::widget::{Container, Icon, container, menu, scrollable, segmented_button};

View file

@ -4,11 +4,15 @@
//! A container which displays an overlay when a popup widget is attached.
use iced::widget;
use iced_core::event::{self, Event};
use iced_core::event::Event;
use iced_core::layout;
use iced_core::mouse;
use iced_core::overlay;
use iced_core::renderer;
use iced_core::touch;
use iced_core::widget::{Operation, Tree};
use iced_core::{
Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget, layout,
mouse, overlay, renderer, touch,
Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget,
};
pub use iced_widget::container::{Catalog, Style};

View file

@ -1,115 +0,0 @@
use crate::anim::smootherstep;
use iced::time::Instant;
use std::time::Duration;
const LAG: f32 = 0.1;
#[derive(Default)]
pub struct Progress {
pub current: f32,
target: Option<f32>,
last: Option<Instant>,
}
impl Progress {
/// Smoothly chases `target` using exponential decay.
/// Returns `true` if a redraw should be requested.
pub fn update(&mut self, target: f32, now: Instant) -> bool {
// Don't animate on start
let Some(last) = self.last else {
self.current = target;
self.target = Some(target);
self.last = Some(now);
return false;
};
// Sync animation clock when target changes
if self.target != Some(target) {
self.target = Some(target);
self.last = Some(now);
return true;
}
let dt = (now - last).as_secs_f32();
self.last = Some(now);
let diff = target - self.current;
if diff.abs() > 0.001 {
self.current += diff * (1.0 - (-dt / LAG).exp());
true
} else {
self.current = target;
false
}
}
}
#[derive(Clone, Copy)]
pub struct Animation {
expanding: bool,
start: Instant,
last: Instant,
offset: u32,
}
impl Default for Animation {
fn default() -> Self {
let now = Instant::now();
Self {
expanding: true,
start: now,
last: now,
offset: 0,
}
}
}
impl Animation {
pub fn timed_transition(
&self,
cycle_duration: Duration,
period: Duration,
wrap: f32,
now: Instant,
) -> Self {
let additional =
((now - self.last).as_secs_f32() / period.as_secs_f32() * u32::MAX as f32) as u32;
let new_offset = self.offset.wrapping_add(additional);
if !cycle_duration.is_zero() && now.duration_since(self.start) > cycle_duration {
let offset = if self.expanding {
new_offset
} else {
new_offset.wrapping_add((wrap * u32::MAX as f32) as u32)
};
Self {
expanding: !self.expanding,
start: now,
last: now,
offset,
}
} else {
Self {
last: now,
offset: new_offset,
..*self
}
}
}
pub fn bar_positions(&self, cycle_duration: Duration, min: f32, wrap: f32) -> (f32, f32) {
let offset = self.offset as f32 / u32::MAX as f32;
let progress = if !cycle_duration.is_zero() {
smootherstep(
self.last.duration_since(self.start).as_secs_f32() / cycle_duration.as_secs_f32(),
)
} else {
1.0
};
if self.expanding {
(offset, offset + min + wrap * progress)
} else {
(offset + wrap * progress, offset + min + wrap)
}
}
}

View file

@ -1,10 +1,15 @@
//! Show a circular progress indicator.
use super::animation::{Animation, Progress};
use super::style::StyleSheet;
use crate::anim::smootherstep;
use iced::advanced::layout;
use iced::advanced::renderer;
use iced::advanced::widget::tree::{self, Tree};
use iced::advanced::{self, Clipboard, Layout, Shell, Widget, layout, renderer};
use iced::advanced::{self, Clipboard, Layout, Shell, Widget};
use iced::mouse;
use iced::time::Instant;
use iced::widget::canvas;
use iced::{Element, Event, Length, Radians, Rectangle, Renderer, Size, Vector, mouse, window};
use iced::window;
use iced::{Element, Event, Length, Radians, Rectangle, Renderer, Size, Vector};
use std::f32::consts::PI;
use std::time::Duration;
@ -18,9 +23,9 @@ where
{
size: f32,
bar_height: f32,
style: Theme::Style,
style: <Theme as StyleSheet>::Style,
cycle_duration: Duration,
period: Duration,
rotation_duration: Duration,
progress: Option<f32>,
}
@ -33,9 +38,9 @@ where
Circular {
size: 40.0,
bar_height: 4.0,
style: Theme::Style::default(),
style: <Theme as StyleSheet>::Style::default(),
cycle_duration: Duration::from_millis(1500),
period: Duration::from_secs(2),
rotation_duration: Duration::from_secs(2),
progress: None,
}
}
@ -53,7 +58,7 @@ where
}
/// Sets the style variant of this [`Circular`].
pub fn style(mut self, style: Theme::Style) -> Self {
pub fn style(mut self, style: <Theme as StyleSheet>::Style) -> Self {
self.style = style;
self
}
@ -64,10 +69,10 @@ where
self
}
/// Sets the base period of this [`Circular`]. This is the duration that a full rotation
/// would take if the cycle duration were set to 0.0 (no expanding or contracting)
pub fn period(mut self, duration: Duration) -> Self {
self.period = duration;
/// Sets the base rotation duration of this [`Circular`]. This is the duration that a full
/// rotation would take if the cycle rotation were set to 0.0 (no expanding or contracting)
pub fn rotation_duration(mut self, duration: Duration) -> Self {
self.rotation_duration = duration;
self
}
@ -77,10 +82,10 @@ where
self
}
fn min_wrap(&self, track_radius: f32) -> (f32, f32) {
fn min_wrap_angle(&self, track_radius: f32) -> (f32, f32) {
let cap_angle = self.bar_height / track_radius;
let gap = MIN_ANGLE.0.max(cap_angle);
((gap - cap_angle) / (2.0 * PI), 1.0 - gap / PI)
(gap - cap_angle, 2.0 * PI - gap * 2.0)
}
}
@ -93,11 +98,125 @@ where
}
}
#[derive(Clone, Copy)]
enum Animation {
Expanding {
start: Instant,
progress: f32,
rotation: u32,
last: Instant,
},
Contracting {
start: Instant,
progress: f32,
rotation: u32,
last: Instant,
},
}
impl Default for Animation {
fn default() -> Self {
Self::Expanding {
start: Instant::now(),
progress: 0.0,
rotation: 0,
last: Instant::now(),
}
}
}
impl Animation {
fn next(&self, additional_rotation: u32, wrap_angle: f32, now: Instant) -> Self {
match self {
Self::Expanding { rotation, .. } => Self::Contracting {
start: now,
progress: 0.0,
rotation: rotation.wrapping_add(additional_rotation),
last: now,
},
Self::Contracting { rotation, .. } => Self::Expanding {
start: now,
progress: 0.0,
rotation: rotation.wrapping_add(
(f64::from((wrap_angle) / (2.0 * PI)) * f64::from(u32::MAX)) as u32,
),
last: now,
},
}
}
fn start(&self) -> Instant {
match self {
Self::Expanding { start, .. } | Self::Contracting { start, .. } => *start,
}
}
fn last(&self) -> Instant {
match self {
Self::Expanding { last, .. } | Self::Contracting { last, .. } => *last,
}
}
fn timed_transition(
&self,
cycle_duration: Duration,
rotation_duration: Duration,
wrap_angle: f32,
now: Instant,
) -> Self {
let elapsed = now.duration_since(self.start());
let additional_rotation = ((now - self.last()).as_secs_f32()
/ rotation_duration.as_secs_f32()
* (u32::MAX) as f32) as u32;
match elapsed {
elapsed if elapsed > cycle_duration => self.next(additional_rotation, wrap_angle, now),
_ => self.with_elapsed(cycle_duration, additional_rotation, elapsed, now),
}
}
fn with_elapsed(
&self,
cycle_duration: Duration,
additional_rotation: u32,
elapsed: Duration,
now: Instant,
) -> Self {
let progress = elapsed.as_secs_f32() / cycle_duration.as_secs_f32();
match self {
Self::Expanding {
start, rotation, ..
} => Self::Expanding {
start: *start,
progress,
rotation: rotation.wrapping_add(additional_rotation),
last: now,
},
Self::Contracting {
start, rotation, ..
} => Self::Contracting {
start: *start,
progress,
rotation: rotation.wrapping_add(additional_rotation),
last: now,
},
}
}
fn rotation(&self) -> f32 {
match self {
Self::Expanding { rotation, .. } | Self::Contracting { rotation, .. } => {
*rotation as f32 / u32::MAX as f32
}
}
}
}
#[derive(Default)]
struct State {
animation: Animation,
cache: canvas::Cache,
progress: Progress,
progress: Option<f32>,
}
impl<Message, Theme> Widget<Message, Theme, Renderer> for Circular<Theme>
@ -141,21 +260,28 @@ where
_viewport: &Rectangle,
) {
let state = tree.state.downcast_mut::<State>();
if let Event::Window(window::Event::RedrawRequested(now)) = event {
if let Some(target) = self.progress {
if state.progress.update(target, *now) {
state.cache.clear();
shell.request_redraw();
}
} else {
let (_, wrap) = self.min_wrap(self.size / 2.0 - self.bar_height);
state.animation =
state
.animation
.timed_transition(self.cycle_duration, self.period, wrap, *now);
if self.progress.is_some() {
if !float_cmp::approx_eq!(
f32,
state.progress.unwrap_or_default(),
self.progress.unwrap_or_default()
) {
state.progress = self.progress;
state.cache.clear();
shell.request_redraw();
}
return;
}
if let Event::Window(window::Event::RedrawRequested(now)) = event {
let (_, wrap_angle) = self.min_wrap_angle(self.size / 2.0 - self.bar_height);
state.animation = state.animation.timed_transition(
self.cycle_duration,
self.rotation_duration,
wrap_angle,
*now,
);
state.cache.clear();
shell.request_redraw();
}
}
@ -173,13 +299,11 @@ where
let state = tree.state.downcast_ref::<State>();
let bounds = layout.bounds();
let custom_style = Theme::appearance(theme, &self.style, self.progress.is_some(), true);
let custom_style =
<Theme as StyleSheet>::appearance(theme, &self.style, self.progress.is_some(), true);
let geometry = state.cache.draw(renderer, bounds.size(), |frame| {
let track_radius = frame.width() / 2.0 - self.bar_height;
if track_radius <= 0.0 {
return;
}
let track_path = canvas::Path::circle(frame.center(), track_radius);
frame.stroke(
@ -189,65 +313,133 @@ where
.with_width(self.bar_height),
);
// Converts a track fraction to an angle in radians, with 0 being top of circle
let to_angle = |t: f32| t * 2.0 * PI - PI / 2.0;
if let Some(progress) = self.progress {
// outer border
if let Some(border_color) = custom_style.border_color {
let border_path =
canvas::Path::circle(frame.center(), track_radius + self.bar_height / 2.0);
let draw_cap = |frame: &mut canvas::Frame, t: f32, flip: bool| {
let angle = to_angle(t);
let center = frame.center() + Vector::new(angle.cos(), angle.sin()) * track_radius;
let (start_angle, end_angle) = if flip {
(angle - PI, angle)
} else {
(angle, angle + PI)
};
let mut builder = canvas::path::Builder::new();
builder.arc(canvas::path::Arc {
center,
radius: self.bar_height / 2.0,
start_angle: Radians(start_angle),
end_angle: Radians(end_angle),
});
frame.fill(&builder.build(), custom_style.bar_color);
};
frame.stroke(
&border_path,
canvas::Stroke::default()
.with_color(border_color)
.with_width(1.0),
);
}
let draw_bar = |frame: &mut canvas::Frame, start: f32, end: f32| {
// inner border
if let Some(border_color) = custom_style.border_color {
let border_path =
canvas::Path::circle(frame.center(), track_radius - self.bar_height / 2.0);
frame.stroke(
&border_path,
canvas::Stroke::default()
.with_color(border_color)
.with_width(1.0),
);
}
// bar
let mut builder = canvas::path::Builder::new();
builder.arc(canvas::path::Arc {
center: frame.center(),
radius: track_radius,
start_angle: Radians(to_angle(start)),
end_angle: Radians(to_angle(end)),
start_angle: Radians(-PI / 2.0),
end_angle: Radians(-PI / 2.0 + progress * 2.0 * PI),
});
let bar_path = builder.build();
frame.stroke(
&builder.build(),
&bar_path,
canvas::Stroke::default()
.with_color(custom_style.bar_color)
.with_width(self.bar_height),
);
draw_cap(frame, end, false);
draw_cap(frame, start, true);
};
if self.progress.is_some() {
if let Some(border_color) = custom_style.border_color {
for radius_offset in [self.bar_height / 2.0, -(self.bar_height / 2.0)] {
let border_path =
canvas::Path::circle(frame.center(), track_radius + radius_offset);
frame.stroke(
&border_path,
canvas::Stroke::default()
.with_color(border_color)
.with_width(1.0),
);
}
}
draw_bar(frame, 0.0, state.progress.current);
let mut builder = canvas::path::Builder::new();
// get center of end of arc for rounded cap
let end_angle = -PI / 2.0 + progress * 2.0 * PI;
let end_center =
frame.center() + Vector::new(end_angle.cos(), end_angle.sin()) * track_radius;
builder.arc(canvas::path::Arc {
center: end_center,
radius: self.bar_height / 2.0,
start_angle: Radians(end_angle),
end_angle: Radians(end_angle + PI),
});
// get center of start of arc for rounded cap
let start_angle = -PI / 2.0;
let start_center = frame.center()
+ Vector::new(start_angle.cos(), start_angle.sin()) * track_radius;
builder.arc(canvas::path::Arc {
center: start_center,
radius: self.bar_height / 2.0,
start_angle: Radians(start_angle - PI),
end_angle: Radians(start_angle),
});
let cap_path = builder.build();
frame.fill(&cap_path, custom_style.bar_color);
} else {
let (min, wrap) = self.min_wrap(track_radius);
let (start, end) = state
.animation
.bar_positions(self.cycle_duration, min, wrap);
draw_bar(frame, start, end);
let mut builder = canvas::path::Builder::new();
let start = state.animation.rotation() * 2.0 * PI;
let (min_angle, wrap_angle) = self.min_wrap_angle(track_radius);
let (start_angle, end_angle) = match state.animation {
Animation::Expanding { progress, .. } => (
start,
start + min_angle + wrap_angle * smootherstep(progress),
),
Animation::Contracting { progress, .. } => (
start + wrap_angle * smootherstep(progress),
start + min_angle + wrap_angle,
),
};
builder.arc(canvas::path::Arc {
center: frame.center(),
radius: track_radius,
start_angle: Radians(start_angle),
end_angle: Radians(end_angle),
});
let bar_path = builder.build();
frame.stroke(
&bar_path,
canvas::Stroke::default()
.with_color(custom_style.bar_color)
.with_width(self.bar_height),
);
let mut builder = canvas::path::Builder::new();
// get center of end of arc for rounded cap
let end_center =
frame.center() + Vector::new(end_angle.cos(), end_angle.sin()) * track_radius;
builder.arc(canvas::path::Arc {
center: end_center,
radius: self.bar_height / 2.0,
start_angle: Radians(end_angle),
end_angle: Radians(end_angle + PI),
});
// get center of start of arc for rounded cap
let start_center = frame.center()
+ Vector::new(start_angle.cos(), start_angle.sin()) * track_radius;
builder.arc(canvas::path::Arc {
center: start_center,
radius: self.bar_height / 2.0,
start_angle: Radians(start_angle - PI),
end_angle: Radians(start_angle),
});
let cap_path = builder.build();
frame.fill(&cap_path, custom_style.bar_color);
}
});

View file

@ -1,15 +1,19 @@
//! Show a linear progress indicator.
use super::animation::{Animation, Progress};
use super::style::StyleSheet;
use iced::advanced::layout;
use iced::advanced::renderer::{self, Quad};
use iced::advanced::widget::tree::{self, Tree};
use iced::advanced::{self, Clipboard, Layout, Shell, Widget, layout, renderer};
use iced::{Background, Element, Event, Length, Pixels, Rectangle, Size, mouse, window};
use iced::advanced::{self, Clipboard, Layout, Shell, Widget};
use iced::mouse;
use iced::time::Instant;
use iced::window;
use iced::{Background, Element, Event, Length, Rectangle, Size};
use crate::anim::smootherstep;
use super::style::StyleSheet;
use std::time::Duration;
const MIN_LENGTH: f32 = 0.15;
const WRAP_LENGTH: f32 = 0.618; // avoids animation repetition
#[must_use]
pub struct Linear<Theme>
where
@ -19,10 +23,7 @@ where
girth: Length,
style: Theme::Style,
cycle_duration: Duration,
period: Duration,
progress: Option<f32>,
markers: Vec<f32>,
segment_spacing: f32,
}
impl<Theme> Linear<Theme>
@ -36,10 +37,7 @@ where
girth: Length::Fixed(4.0),
style: Theme::Style::default(),
cycle_duration: Duration::from_millis(1500),
period: Duration::from_secs(2),
progress: None,
markers: Vec::new(),
segment_spacing: 0.0,
}
}
@ -67,38 +65,11 @@ where
self
}
/// Sets the base period of this [`Linear`]. This is the duration that a full traversal
/// would take if the cycle duration were set to 0.0 (no expanding or contracting)
pub fn period(mut self, duration: Duration) -> Self {
self.period = duration;
self
}
/// Override the default behavior by providing a determinate progress value between `0.0` and `1.0`.
pub fn progress(mut self, progress: f32) -> Self {
self.progress = Some(progress.clamp(0.0, 1.0));
self
}
/// Sets the markers of a determinate progress bar, which divide the bar into segments.
/// Each value is a progress fraction between `0.0` and `1.0 at which a visual gap is inserted.
pub fn markers(mut self, markers: impl Into<Vec<f32>>) -> Self {
let mut markers = markers.into();
for bp in &mut markers {
*bp = bp.clamp(0.0, 1.0);
}
markers.sort_by(f32::total_cmp);
markers.dedup();
self.markers = markers;
self
}
/// Sets the spacing between segments at each marker.
pub fn segment_spacing(mut self, spacing: impl Into<Pixels>) -> Self {
self.segment_spacing = spacing.into().0;
self
}
}
impl<Theme> Default for Linear<Theme>
@ -110,10 +81,63 @@ where
}
}
#[derive(Default)]
struct State {
animation: Animation,
progress: Progress,
#[derive(Clone, Copy)]
enum State {
Expanding { start: Instant, progress: f32 },
Contracting { start: Instant, progress: f32 },
}
impl Default for State {
fn default() -> Self {
Self::Expanding {
start: Instant::now(),
progress: 0.0,
}
}
}
impl State {
fn next(&self, now: Instant) -> Self {
match self {
Self::Expanding { .. } => Self::Contracting {
start: now,
progress: 0.0,
},
Self::Contracting { .. } => Self::Expanding {
start: now,
progress: 0.0,
},
}
}
fn start(&self) -> Instant {
match self {
Self::Expanding { start, .. } | Self::Contracting { start, .. } => *start,
}
}
fn timed_transition(&self, cycle_duration: Duration, now: Instant) -> Self {
let elapsed = now.duration_since(self.start());
match elapsed {
elapsed if elapsed > cycle_duration => self.next(now),
_ => self.with_elapsed(cycle_duration, elapsed),
}
}
fn with_elapsed(&self, cycle_duration: Duration, elapsed: Duration) -> Self {
let progress = elapsed.as_secs_f32() / cycle_duration.as_secs_f32();
match self {
Self::Expanding { start, .. } => Self::Expanding {
start: *start,
progress,
},
Self::Contracting { start, .. } => Self::Contracting {
start: *start,
progress,
},
}
}
}
impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> for Linear<Theme>
@ -157,21 +181,16 @@ where
shell: &mut Shell<'_, Message>,
_viewport: &Rectangle,
) {
if self.progress.is_some() {
return;
}
let state = tree.state.downcast_mut::<State>();
if let Event::Window(window::Event::RedrawRequested(now)) = event {
if let Some(target) = self.progress {
if state.progress.update(target, *now) {
shell.request_redraw();
}
} else {
state.animation = state.animation.timed_transition(
self.cycle_duration,
self.period,
WRAP_LENGTH,
*now,
);
shell.request_redraw();
}
*state = state.timed_transition(self.cycle_duration, *now);
shell.request_redraw();
}
}
@ -189,148 +208,88 @@ where
let custom_style = theme.appearance(&self.style, self.progress.is_some(), false);
let state = tree.state.downcast_ref::<State>();
let border_width = if custom_style.border_color.is_some() {
1.0
} else {
0.0
};
let border_color = custom_style.border_color.unwrap_or(custom_style.bar_color);
let radius = custom_style.border_radius;
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle {
x: bounds.x,
y: bounds.y,
width: bounds.width,
height: bounds.height,
},
border: iced::Border {
width: if custom_style.border_color.is_some() {
1.0
} else {
0.0
},
color: custom_style.border_color.unwrap_or(custom_style.bar_color),
radius: custom_style.border_radius.into(),
},
snap: true,
..renderer::Quad::default()
},
Background::Color(custom_style.track_color),
);
let mut draw_quad = |x: f32, width: f32, color: iced::Color, border: iced::Border| {
// don't draw if width is less than 0.1 pixels
if width * bounds.width > 0.1 {
renderer.fill_quad(
if let Some(progress) = self.progress {
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle {
x: bounds.x,
y: bounds.y,
width: progress * bounds.width,
height: bounds.height,
},
border: iced::Border {
width: 0.,
color: iced::Color::TRANSPARENT,
radius: custom_style.border_radius.into(),
},
snap: true,
..renderer::Quad::default()
},
Background::Color(custom_style.bar_color),
);
} else {
match state {
State::Expanding { progress, .. } => renderer.fill_quad(
renderer::Quad {
bounds: Rectangle {
x: bounds.x + x * bounds.width,
x: bounds.x,
y: bounds.y,
width: width * bounds.width,
width: smootherstep(*progress) * bounds.width,
height: bounds.height,
},
border,
border: iced::Border {
width: 0.,
color: iced::Color::TRANSPARENT,
radius: custom_style.border_radius.into(),
},
snap: true,
..renderer::Quad::default()
},
Background::Color(color),
);
}
};
Background::Color(custom_style.bar_color),
),
if self.progress.is_some() {
let spacing = self.segment_spacing.max(1.0);
let radius_inner = radius.min(spacing);
let gap = if self.markers.is_empty() {
0.0
} else {
spacing / bounds.width
};
let drawable = 1.0 - gap * self.markers.len() as f32;
let num_segments = self.markers.len() + 1;
let segment_bounds = |i: usize| {
let seg_lo = if i == 0 { 0.0 } else { self.markers[i - 1] };
let seg_hi = if i == num_segments - 1 {
1.0
} else {
self.markers[i]
};
(seg_lo, seg_hi)
};
let get_radius = |i: usize| {
let r_left = if i == 0 { radius } else { radius_inner };
let r_right = if i == num_segments - 1 {
radius
} else {
radius_inner
};
[r_left, r_right, r_right, r_left].into()
};
// draw track segments
for i in 0..num_segments {
let (seg_lo, seg_hi) = segment_bounds(i);
let x_start = seg_lo * drawable + i as f32 * gap;
let x_width = (seg_hi - seg_lo) * drawable;
draw_quad(
x_start,
x_width,
custom_style.track_color,
iced::Border {
width: border_width,
color: border_color,
radius: get_radius(i),
State::Contracting { progress, .. } => renderer.fill_quad(
Quad {
bounds: Rectangle {
x: bounds.x + smootherstep(*progress) * bounds.width,
y: bounds.y,
width: (1.0 - smootherstep(*progress)) * bounds.width,
height: bounds.height,
},
border: iced::Border {
width: 0.,
color: iced::Color::TRANSPARENT,
radius: custom_style.border_radius.into(),
},
snap: true,
..renderer::Quad::default()
},
);
Background::Color(custom_style.bar_color),
),
}
// draw bar segments
let current_p = state.progress.current;
for i in 0..num_segments {
let (seg_lo, seg_hi) = segment_bounds(i);
// don't iterate over non-filled segments
if current_p < seg_lo {
break;
}
let x_start = seg_lo * drawable + i as f32 * gap;
let x_width = (seg_hi - seg_lo) * drawable;
let fill = ((current_p - seg_lo) / (seg_hi - seg_lo)).clamp(0.0, 1.0);
draw_quad(
x_start,
x_width * fill,
custom_style.bar_color,
iced::Border {
radius: get_radius(i),
..iced::Border::default()
},
);
}
} else {
// draw track
draw_quad(
0.0,
1.0,
custom_style.track_color,
iced::Border {
width: border_width,
color: border_color,
radius: radius.into(),
},
);
// draw bar
let (bar_start, bar_end) =
state
.animation
.bar_positions(self.cycle_duration, MIN_LENGTH, WRAP_LENGTH);
let length = bar_end - bar_start;
let start = bar_start % 1.0;
let right_width = (1.0 - start).min(length);
let left_width = length - right_width;
draw_quad(
start,
right_width,
custom_style.bar_color,
iced::Border {
radius: radius.into(),
..iced::Border::default()
},
);
draw_quad(
0.0,
left_width,
custom_style.bar_color,
iced::Border {
radius: radius.into(),
..iced::Border::default()
},
);
}
}
}

View file

@ -1,4 +1,3 @@
mod animation;
pub mod circular;
pub mod linear;
pub mod style;

View file

@ -1,11 +1,15 @@
//! Create choices using radio buttons.
use crate::{Theme, theme};
use iced::border;
use iced_core::event::{self, Event};
use iced_core::event::Event;
use iced_core::layout;
use iced_core::mouse;
use iced_core::overlay;
use iced_core::renderer;
use iced_core::touch;
use iced_core::widget::tree::Tree;
use iced_core::{
Border, Clipboard, Element, Layout, Length, Pixels, Rectangle, Shell, Size, Vector, Widget,
layout, mouse, overlay, renderer, touch,
};
use iced_widget::radio as iced_radio;

View file

@ -5,14 +5,14 @@ use iced::futures::channel::mpsc::UnboundedSender;
use iced::widget::Container;
pub use subscription::*;
use iced_core::event::{self, Event};
use iced_core::event::Event;
use iced_core::layout;
use iced_core::mouse;
use iced_core::overlay;
use iced_core::renderer;
use iced_core::widget::Tree;
use iced_core::{
Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget, layout,
mouse, overlay, renderer,
};
use std::fmt::Debug;
use std::hash::Hash;
use iced_core::{Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget};
use std::{fmt::Debug, hash::Hash};
pub use iced_widget::container::{Catalog, Style};

View file

@ -1,10 +1,13 @@
use iced::Rectangle;
use iced::futures::channel::mpsc::{UnboundedReceiver, unbounded};
use iced::futures::{StreamExt, stream};
use iced::{
Rectangle,
futures::{
StreamExt,
channel::mpsc::{UnboundedReceiver, unbounded},
stream,
},
};
use iced_futures::Subscription;
use std::collections::HashMap;
use std::fmt::Debug;
use std::hash::Hash;
use std::{collections::HashMap, fmt::Debug, hash::Hash};
use super::RectangleTracker;

View file

@ -1,12 +1,13 @@
//! Responsive Container, which will notify of size changes.
use iced::{Limits, Size};
use iced_core::event::{self, Event};
use iced_core::event::Event;
use iced_core::layout;
use iced_core::mouse;
use iced_core::overlay;
use iced_core::renderer;
use iced_core::widget::{Id, Operation, Tree, tree};
use iced_core::{
Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget, layout, mouse, overlay,
renderer,
};
use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget};
pub(crate) fn responsive_container<'a, Message: 'static, Theme, E>(
content: E,

View file

@ -2,8 +2,10 @@ use std::collections::HashMap;
use apply::Apply;
use crate::widget::{button, icon, responsive_container};
use crate::{Core, Element};
use crate::{
Core, Element,
widget::{button, icon, responsive_container},
};
use super::menu::{self, ItemHeight, ItemWidth};

View file

@ -25,11 +25,9 @@ use iced_core::id::Internal;
use iced_core::mouse::ScrollDelta;
use iced_core::text::{self, Ellipsize, LineHeight, Renderer as TextRenderer, Shaping, Wrapping};
use iced_core::widget::operation::Focusable;
use iced_core::widget::{self, Tree, operation, tree};
use iced_core::{
Border, Clipboard, Layout, Point, Renderer as IcedRenderer, Shadow, Shell, Text, Widget,
layout, renderer,
};
use iced_core::widget::{self, operation, tree};
use iced_core::{Border, Point, Renderer as IcedRenderer, Shadow, Text};
use iced_core::{Clipboard, Layout, Shell, Widget, layout, renderer, widget::Tree};
use iced_runtime::{Action, task};
use slotmap::{Key, SecondaryMap};
use std::borrow::Cow;
@ -1448,8 +1446,6 @@ where
state.context_cursor = cursor_position.position().unwrap_or_default();
state.menu_state.inner.with_data_mut(|data| {
// Clear stale MenuBounds from any previous context menu before opening a new one.
data.reset();
data.open = true;
data.view_cursor = cursor_position;
});

Some files were not shown because too many files have changed in this diff Show more