Compare commits

..

14 commits

Author SHA1 Message Date
f7bdd84ae4 chore: use local graphics dependencies 2026-05-25 19:36:38 +02:00
4a00191ac9 chore: point iced at local glyphon 2026-05-25 18:28:10 +02:00
d4d421511a chore: use local COSMIC support crates 2026-05-25 18:09:50 +02:00
f3db54342c fix: drop iced_winit/single-instance feature ref
Upstream dc84488c "fix(iced): double IME commit on GNOME desktop" wires
the libcosmic single-instance feature through iced_winit/single-instance,
but our pinned iced fork (yoda-wayland-only @ 81639935) does not carry
that feature yet. Drop the ref so libcosmic-yoda compiles after rebase;
the GNOME IME fix needs to be ported separately into the iced submodule
before this can be reinstated.

Leyoda 2026 – GPLv3
2026-05-25 13:13:30 +02:00
6722f1e581 yoda: use local dbus settings bindings 2026-05-25 13:10:12 +02:00
5f50d74477 feat(flex_row): reorderable rows with shadow constant gated (squashed)
Squash of 2 yoda commits:
- 7dd0ee83 feat: reorderable flex row
- b1b8203a fix: gate reorderable flex row shadow constant
2026-05-25 13:08:39 +02:00
7a191cf086 yoda: warning cleanup sweep (dead code + clippy --fix) (squashed)
Squash of 4 yoda commits:
- 84437e21 yoda: libcosmic-yoda dead-code purge (14->0 warnings)
- 999db0a4 yoda: cosmic-theme cleanup (4->0 warnings) — workspace at 0 warnings total
- 4743bb8e yoda: cargo clippy --fix on libcosmic-yoda (115->33 warnings)
- 675f3b59 chore: reduce local stack warnings
2026-05-25 13:07:48 +02:00
38acba82b4 yoda: bump iced submodule + cargo auto-fix sweeps (squashed)
Squash of 6 yoda commits:
- 282813c8 yoda: bump iced -> window_clipboard via public Forgejo fork
- cdf34938 yoda: cargo fix --lib (libcosmic-yoda) — drop 99 trivial warnings
- 38a988cb yoda: cargo fix on cosmic-config + bump iced auto-fix commit
- 301bbf6e yoda: bump iced submodule -> iced_winit warning cleanup (0 left)
- b94c03d9 yoda: bump iced submodule -> iced_widget cleanup (0 left)
- a9492d76 yoda: bump iced submodule -> all iced crates at 0 warnings
2026-05-25 13:07:13 +02:00
ef8f69134f 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-25 13:05:50 +02:00
597aba3e9f yoda: bump iced submodule (Wayland-only + softbuffer cuts) (squashed)
Squash of 3 yoda commits:
- c118f5a2 yoda: bump iced submodule ref -> yoda-wayland-only HEAD 8f6be798
- 388e0655 yoda: bump iced submodule -> softbuffer + window_clipboard cuts
- 9b2a3643 Update iced warning fixes
2026-05-25 13:05:22 +02:00
e3dcdf1fce yoda: fork pivot — Wayland-only + ungate winit + soft-fork libcosmic-yoda (squashed)
Squash of 7 yoda commits forming the fork pivot:
- 255cf7cc rename: libcosmic -> libcosmic-yoda (fork 0.1.0-yoda)
- 8701aa31 feat(yoda): Wayland-only cut — drop winit and x11 features
- 6736a596 yoda: soft-fork pivot — keep Cargo name 'libcosmic' for dep unification
- 3e23d087 yoda: re-apply hard rename — libcosmic -> libcosmic-yoda (0.1.0-yoda)
- aec3eb61 yoda: ungate remaining winit+wayland combined cfgs
- 8ab7b158 yoda-v2: color_picker Theme ref + context_menu/menu ungate winit
- 8d1d8739 yoda: drop x11 defaults on iced_winit + iced_tiny_skia
2026-05-25 13:05:05 +02:00
10422b8f4a 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-05-25 13:04:22 +02:00
87510782ae feat(segmented_button): on_double_click + internal tab reorder (squashed)
Squash of 2 yoda commits:
- 108441ef segmented_button: add on_double_click callback
- a322516f segmented_button: fix internal tab reorder end-to-end
2026-05-25 13:03:13 +02:00
ea17ada931 perf: throttle malloc_trim + avoid VecDeque clones (squashed)
Squash of 2 yoda commits:
- 77262dd0 perf(malloc): throttle malloc_trim to 1 Hz in hot paths
- 1d98eee6 perf(widget): avoid VecDeque clone in segmented_button/table Model::clear
2026-05-25 13:03:13 +02:00
7 changed files with 129 additions and 332 deletions

View file

@ -123,6 +123,8 @@ impl cosmic::Application for Window {
}, },
Some(Box::new(move |state: &Window| { Some(Box::new(move |state: &Window| {
let content_list = list_column() let content_list = list_column()
.padding(5)
.spacing(0)
.add(settings::item( .add(settings::item(
"Example row", "Example row",
cosmic::widget::container( cosmic::widget::container(

2
iced

@ -1 +1 @@
Subproject commit d9fe129f7316cea3ef6d2576e9421a305759b8bf Subproject commit de1247123a631087d2f78831c99f5f1b66d5a582

View file

@ -23,12 +23,6 @@ check-json: (check '--message-format=json')
clean: clean:
cargo clean cargo clean
# Generate documentation
doc:
env RUSTDOCFLAGS="--cfg docsrs" cargo +nightly-2026-04-27 doc --no-deps --verbose \
-p cosmic-client-toolkit -p cosmic-protocols -p libcosmic \
--features tokio,winit,wayland,desktop,single-instance,applet,xdg-portal,multi-window
# Also remove .cargo and vendored dependencies # Also remove .cargo and vendored dependencies
clean-dist: clean clean-dist: clean
rm -rf .cargo vendor vendor.tar target rm -rf .cargo vendor vendor.tar target

View file

@ -5,89 +5,6 @@
#![cfg_attr(target_os = "redox", feature(lazy_cell))] #![cfg_attr(target_os = "redox", feature(lazy_cell))]
#![cfg_attr(docsrs, feature(doc_cfg))] #![cfg_attr(docsrs, feature(doc_cfg))]
//! # The COSMIC Toolkit
//!
//! Quickly code your project with modular elements created with customization in mind.
//! Panels, applets, theming, tiling, launcher, app library, keyboard shortcuts and
//! dynamic or pinned workspaces are all made flexible to bend to your users needs.
//!
//! [COSMIC](https://system76.com/cosmic) empowers frictionless development by being
//! approachable, easy to maintain, and modular. Use the same language and toolkit to
//! build apps and applets, with helpful templates provided. Even shell components and
//! the compositor use the same toolkit. Learn once and use your knowledge anywhere in
//! the desktop.
//!
//! ## Architecture
//!
//! Based on [iced](https://iced.rs/), COSMIC apps and applets are modeled with the
//! MVU (Model-View-Update) pattern from [The Elm Architecture](https://guide.elm-lang.org/architecture/).
//! For more details, see the [architecture page of the iced book](https://book.iced.rs/architecture.html).
//!
//! An application will consist of:
//!
//! * An application **model** for holding persistent application state which implements
//! the [Application] trait.
//! * A [view](Application::view) function which borrows data from the model to construct
//! a view with stateless widgets
//! * An [update](Application::update) function which receives application messages from
//! widgets, tasks, and subscriptions.
//!
//! Messages handled by the update function are used to update the
//! application model and spawn background tasks that can emit messages back to the
//! app's update function.
//!
//! ### Tasks
//!
//! Tasks returned by the update function are scheduled for concurrent execution on a
//! background thread managed by the application's async executor, which is tokio by default.
//! They can be constructed from futures and may also stream events back to the application
//! asynchronously.
//!
//! ### Subscriptions
//!
//! Applications may also use a [subscription](Application::subscription) function to subscribe to external
//! asynchronous event streams. These can run perpetually from application start; or
//! optionally started, stopped, and restarted based on changes to the application model
//! between updates. Such as:
//!
//! * Conditionally starting and stopping a subscription based on the state of a boolean
//! value or enum
//! * Restarting a subscription when the hash of its borrowed data changes
//! * Dynamically spawning a subscription for each item in a list.
//!
//! ## Templates
//!
//! Get started using [cargo-generate](https://github.com/cargo-generate/cargo-generate)
//! with one of the following templates. The app template is for developing desktop
//! applications and the applet template is for developing COSMIC applets.
//!
//! - [App Template](https://github.com/pop-os/cosmic-app-template/)
//! - [Applet Template](https://github.com/pop-os/cosmic-applet-template/)
//!
//! ## Widgets
//!
//! Reference the [`widget`] module for available widgets for use in the view function.
//! Widgets are composable and can be configured through chainable builder methods.
//! Compose widgets together to create complex interfaces and higher level widgets.
//!
//! Composed widgets may be managed by their own custom type with its own view and
//! update functions. It is a common pattern to use these functions within the
//! application's own view and update functions. If using a custom type, implement
//! [From] for the [Element] type to have API treat a composed widget the same as a
//! native custom widget.
//!
//! If a widget does not exist for a specific use case, use the [Widget](iced::advanced::Widget)
//! trait to create an advanced custom widget. This can then be used to composed higher
//! level widgets by chaining composable widgets together.
//!
//! ## Core
//!
//! Every application model requires a [cosmic::Core](app::Core). This contains
//! application state which is managed by libcosmic's runtime for its generated
//! interfaces. Such as the context drawner, nav bar, and the headerbar. This can be
//! used by the app to subscribe to configuration changes and to emit events to the
//! libcosmic-managed portion of the application's state and view.
/// Recommended default imports. /// Recommended default imports.
pub mod prelude { pub mod prelude {
pub use crate::ApplicationExt; pub use crate::ApplicationExt;

View file

@ -51,10 +51,9 @@ impl KeyBind {
physical_key: Option<&Physical>, physical_key: Option<&Physical>,
) -> bool { ) -> bool {
let key_eq = self.key_eq(key) let key_eq = self.key_eq(key)
|| (!is_latin_shortcut_key(key) || physical_key
&& physical_key
.and_then(physical_key_to_latin) .and_then(physical_key_to_latin)
.is_some_and(|latin| self.key_eq(&latin))); .is_some_and(|latin| self.key_eq(&latin));
key_eq key_eq
&& modifiers.logo() == self.modifiers.contains(&Modifier::Super) && modifiers.logo() == self.modifiers.contains(&Modifier::Super)
&& modifiers.control() == self.modifiers.contains(&Modifier::Ctrl) && modifiers.control() == self.modifiers.contains(&Modifier::Ctrl)
@ -71,19 +70,6 @@ impl KeyBind {
} }
} }
fn is_latin_shortcut_key(key: &Key) -> bool {
let Key::Character(s) = key else {
return false;
};
let mut chars = s.chars();
let Some(ch) = chars.next() else {
return false;
};
chars.next().is_none() && (ch.is_ascii_graphic() || ch == ' ')
}
/// Converts a physical key code to the corresponding US-layout Latin `Key`. /// Converts a physical key code to the corresponding US-layout Latin `Key`.
/// ///
/// This mapping is intentionally limited to keys that may produce different /// This mapping is intentionally limited to keys that may produce different
@ -152,92 +138,3 @@ impl fmt::Display for KeyBind {
} }
} }
} }
#[cfg(test)]
mod test {
use super::*;
fn bind_ctrl_w() -> KeyBind {
KeyBind {
modifiers: vec![Modifier::Ctrl],
key: Key::Character("w".into()),
}
}
#[test]
fn ctrl_w() {
assert!(bind_ctrl_w().matches(
Modifiers::CTRL,
&Key::Character("w".into()),
Some(&Physical::Code(Code::KeyW)),
));
}
#[test]
fn ctrl_w_no_fallback_to_dvorak_comma() {
assert!(!bind_ctrl_w().matches(
Modifiers::CTRL,
&Key::Character(",".into()),
Some(&Physical::Code(Code::KeyW)),
));
}
#[test]
fn non_latin_layout_fallback() {
assert!(bind_ctrl_w().matches(
Modifiers::CTRL,
&Key::Character("ц".into()),
Some(&Physical::Code(Code::KeyW)),
));
let bind = KeyBind {
modifiers: vec![Modifier::Ctrl],
key: Key::Character("s".into()),
};
assert!(bind.matches(
Modifiers::CTRL,
&Key::Character("ы".into()),
Some(&Physical::Code(Code::KeyS)),
));
assert!(!bind.matches(
Modifiers::CTRL,
&Key::Character("ц".into()),
Some(&Physical::Code(Code::KeyQ)),
));
}
#[test]
fn ctrl_space() {
let bind = KeyBind {
modifiers: vec![Modifier::Ctrl],
key: Key::Character(" ".into()),
};
assert!(bind.matches(Modifiers::CTRL, &Key::Character(" ".into()), None,));
}
#[test]
fn ctrl_space_no_fallback() {
assert!(!bind_ctrl_w().matches(
Modifiers::CTRL,
&Key::Character(" ".into()),
Some(&Physical::Code(Code::KeyW)),
));
}
#[test]
fn ctrl_a_no_fallback_to_french_azerty_q() {
let bind = KeyBind {
modifiers: vec![Modifier::Ctrl],
key: Key::Character("a".into()),
};
assert!(!bind.matches(
Modifiers::CTRL,
&Key::Character("q".into()),
Some(&Physical::Code(Code::KeyA)),
));
}
}

View file

@ -9,8 +9,7 @@ use iced::{Element, Event, Length, Radians, Rectangle, Renderer, Size, Vector, m
use std::f32::consts::PI; use std::f32::consts::PI;
use std::time::Duration; use std::time::Duration;
const MIN_GAP_ANGLE: Radians = Radians(PI / 4.0); const MIN_ANGLE: Radians = Radians(PI / 8.0);
const MAX_WRAP: f32 = 1.0 - MIN_GAP_ANGLE.0 / (2.0 * PI);
#[must_use] #[must_use]
pub struct Circular<Theme> pub struct Circular<Theme>
@ -77,6 +76,12 @@ where
self.progress = Some(progress.clamp(0.0, 1.0)); self.progress = Some(progress.clamp(0.0, 1.0));
self self
} }
fn min_wrap(&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)
}
} }
impl<Theme> Default for Circular<Theme> impl<Theme> Default for Circular<Theme>
@ -143,12 +148,11 @@ where
shell.request_redraw(); shell.request_redraw();
} }
} else { } else {
state.animation = state.animation.timed_transition( let (_, wrap) = self.min_wrap(self.size / 2.0 - self.bar_height);
self.cycle_duration, state.animation =
self.period, state
MAX_WRAP, .animation
*now, .timed_transition(self.cycle_duration, self.period, wrap, *now);
);
state.cache.clear(); state.cache.clear();
shell.request_redraw(); shell.request_redraw();
} }
@ -187,6 +191,25 @@ where
// Converts a track fraction to an angle in radians, with 0 being top of circle // 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; let to_angle = |t: f32| t * 2.0 * PI - PI / 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);
};
let draw_bar = |frame: &mut canvas::Frame, start: f32, end: f32| { let draw_bar = |frame: &mut canvas::Frame, start: f32, end: f32| {
let mut builder = canvas::path::Builder::new(); let mut builder = canvas::path::Builder::new();
builder.arc(canvas::path::Arc { builder.arc(canvas::path::Arc {
@ -199,9 +222,10 @@ where
&builder.build(), &builder.build(),
canvas::Stroke::default() canvas::Stroke::default()
.with_color(custom_style.bar_color) .with_color(custom_style.bar_color)
.with_width(self.bar_height) .with_width(self.bar_height),
.with_line_cap(canvas::LineCap::Round),
); );
draw_cap(frame, end, false);
draw_cap(frame, start, true);
}; };
if self.progress.is_some() { if self.progress.is_some() {
@ -219,11 +243,10 @@ where
} }
draw_bar(frame, 0.0, state.progress.current); draw_bar(frame, 0.0, state.progress.current);
} else { } else {
// f32::EPSILON prevents flicker when wrap angle is 0.0 let (min, wrap) = self.min_wrap(track_radius);
let (start, end) = let (start, end) = state
state
.animation .animation
.bar_positions(self.cycle_duration, f32::EPSILON, MAX_WRAP); .bar_positions(self.cycle_duration, min, wrap);
draw_bar(frame, start, end); draw_bar(frame, start, end);
} }
}); });

View file

@ -3,7 +3,7 @@ use super::animation::{Animation, Progress};
use super::style::StyleSheet; use super::style::StyleSheet;
use iced::advanced::widget::tree::{self, Tree}; 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, layout, renderer};
use iced::{Element, Event, Length, Pixels, Rectangle, Size, mouse, window}; use iced::{Background, Element, Event, Length, Pixels, Rectangle, Size, mouse, window};
use std::time::Duration; use std::time::Duration;
@ -39,7 +39,7 @@ where
period: Duration::from_secs(2), period: Duration::from_secs(2),
progress: None, progress: None,
markers: Vec::new(), markers: Vec::new(),
segment_spacing: 1.0, segment_spacing: 0.0,
} }
} }
@ -81,11 +81,11 @@ where
} }
/// Sets the markers of a determinate progress bar, which divide the bar into segments. /// Sets the markers of a determinate progress bar, which divide the bar into segments.
/// Each marker is a value between `0.0` and `1.0` that defines the position of a visual gap. /// 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 { pub fn markers(mut self, markers: impl Into<Vec<f32>>) -> Self {
let mut markers = markers.into(); let mut markers = markers.into();
for marker in &mut markers { for bp in &mut markers {
*marker = marker.clamp(0.0, 1.0); *bp = bp.clamp(0.0, 1.0);
} }
markers.sort_by(f32::total_cmp); markers.sort_by(f32::total_cmp);
markers.dedup(); markers.dedup();
@ -96,7 +96,7 @@ where
/// Sets the spacing between segments at each marker. /// Sets the spacing between segments at each marker.
pub fn segment_spacing(mut self, spacing: impl Into<Pixels>) -> Self { pub fn segment_spacing(mut self, spacing: impl Into<Pixels>) -> Self {
self.segment_spacing = spacing.into().0.max(1.0); self.segment_spacing = spacing.into().0;
self self
} }
} }
@ -197,144 +197,110 @@ where
let border_color = custom_style.border_color.unwrap_or(custom_style.bar_color); let border_color = custom_style.border_color.unwrap_or(custom_style.bar_color);
let radius = custom_style.border_radius; let radius = custom_style.border_radius;
let mut draw_quad = |x: f32, let mut draw_quad = |x: f32, width: f32, color: iced::Color, border: iced::Border| {
width: f32, // don't draw if width is less than 0.1 pixels
color: iced::Color, if width * bounds.width > 0.1 {
mut border: iced::Border,
is_track: bool,
total_progress_width: f32| {
let mut height = bounds.height;
if !is_track {
// For progress that is at the end of completion
if total_progress_width > bounds.width - radius {
let border_radius =
radius.min(bounds.height / 2.0) - (bounds.width - total_progress_width);
border.radius.top_right = border_radius;
border.radius.bottom_right = border_radius;
} else {
border.radius.top_right = 0.0;
border.radius.bottom_right = 0.0;
}
// For indeterminate mode or when progress has just started
if x < radius.min(bounds.height / 2.0) {
let border_radius = radius.min(bounds.height / 2.0) - x;
border.radius.top_left = border_radius;
border.radius.bottom_left = border_radius;
if total_progress_width < radius.min(bounds.height / 2.0) {
height = bounds.height - 2.0 * radius.min(bounds.height / 2.0)
+ total_progress_width * 2.0;
}
} else {
border.radius.top_left = 0.0;
border.radius.bottom_left = 0.0;
}
if x > bounds.width - radius.min(bounds.height / 2.0) {
height = bounds.height - 2.0 * radius.min(bounds.height / 2.0) + width * 2.0;
}
}
renderer.fill_quad( renderer.fill_quad(
renderer::Quad { renderer::Quad {
bounds: Rectangle { bounds: Rectangle {
x: bounds.x + x, x: bounds.x + x * bounds.width,
y: bounds.y + (bounds.height - height) / 2.0, y: bounds.y,
width, width: width * bounds.width,
height, height: bounds.height,
}, },
border, border,
snap: true, snap: true,
..renderer::Quad::default() ..renderer::Quad::default()
}, },
color, Background::Color(color),
); );
}
}; };
if self.progress.is_some() { if self.progress.is_some() {
let current_p = state.progress.current; let spacing = self.segment_spacing.max(1.0);
let len = self.markers.len();
let spacing = self.segment_spacing;
let radius_inner = radius.min(spacing); let radius_inner = radius.min(spacing);
let gap = if len != 0 { let gap = if self.markers.is_empty() {
spacing / bounds.width
} else {
0.0 0.0
} else {
spacing / bounds.width
}; };
let drawable = 1.0 - gap * len as f32; let drawable = 1.0 - gap * self.markers.len() as f32;
let num_segments = self.markers.len() + 1;
let mut absolute_width = 0.0; let segment_bounds = |i: usize| {
for i in 0..=len { let seg_lo = if i == 0 { 0.0 } else { self.markers[i - 1] };
let (seg_lo, r_left) = if i == 0 { let seg_hi = if i == num_segments - 1 {
(0.0, radius) 1.0
} else { } else {
(self.markers[i - 1], radius_inner) self.markers[i]
}; };
let (seg_hi, r_right) = if i == len { (seg_lo, seg_hi)
(1.0, radius) };
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 { } else {
(self.markers[i], radius_inner) 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_start = seg_lo * drawable + i as f32 * gap;
let x_width = (seg_hi - seg_lo) * drawable; let x_width = (seg_hi - seg_lo) * drawable;
let mut segment_radius = if i == 0 && len == 0 {
[r_left, r_right, r_right, r_left].into()
} else if i == 0 {
[r_left, 0.0, 0.0, r_left].into()
} else if i == len {
[0.0, r_right, r_right, 0.0].into()
} else {
[0.0, 0.0, 0.0, 0.0].into()
};
// draw track segment
draw_quad( draw_quad(
x_start * bounds.width, x_start,
x_width * bounds.width, x_width,
custom_style.track_color, custom_style.track_color,
iced::Border { iced::Border {
width: border_width, width: border_width,
color: border_color, color: border_color,
radius: segment_radius, radius: get_radius(i),
}, },
true,
bounds.width,
);
// draw bar segment
if current_p > seg_lo {
let fill = ((current_p - seg_lo) / (seg_hi - seg_lo)).min(1.0);
absolute_width += x_width * fill + if i == 0 { 0.0 } else { gap };
segment_radius = [r_left, r_right, r_right, r_left].into();
draw_quad(
x_start * bounds.width,
x_width * fill * bounds.width,
custom_style.bar_color,
iced::Border {
radius: segment_radius,
..iced::Border::default()
},
false,
absolute_width * bounds.width,
); );
} }
// 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 { } else {
// draw track // draw track
draw_quad( draw_quad(
0.0, 0.0,
bounds.width, 1.0,
custom_style.track_color, custom_style.track_color,
iced::Border { iced::Border {
width: border_width, width: border_width,
color: border_color, color: border_color,
radius: radius.into(), radius: radius.into(),
}, },
true,
bounds.width,
); );
// draw bar // draw bar
@ -346,26 +312,24 @@ where
let start = bar_start % 1.0; let start = bar_start % 1.0;
let right_width = (1.0 - start).min(length); let right_width = (1.0 - start).min(length);
let left_width = length - right_width; let left_width = length - right_width;
let border = iced::Border {
radius: radius.into(),
..iced::Border::default()
};
draw_quad( draw_quad(
start * bounds.width, start,
right_width * bounds.width, right_width,
custom_style.bar_color, custom_style.bar_color,
border, iced::Border {
false, radius: radius.into(),
(right_width + start) * bounds.width, ..iced::Border::default()
},
); );
draw_quad( draw_quad(
0.0, 0.0,
left_width * bounds.width, left_width,
custom_style.bar_color, custom_style.bar_color,
border, iced::Border {
false, radius: radius.into(),
left_width * bounds.width, ..iced::Border::default()
},
); );
} }
} }