Compare commits
14 commits
3daac73eff
...
f7bdd84ae4
| Author | SHA1 | Date | |
|---|---|---|---|
| f7bdd84ae4 | |||
| 4a00191ac9 | |||
| d4d421511a | |||
| f3db54342c | |||
| 6722f1e581 | |||
| 5f50d74477 | |||
| 7a191cf086 | |||
| 38acba82b4 | |||
| ef8f69134f | |||
| 597aba3e9f | |||
| e3dcdf1fce | |||
| 10422b8f4a | |||
| 87510782ae | |||
| ea17ada931 |
7 changed files with 129 additions and 332 deletions
|
|
@ -123,6 +123,8 @@ impl cosmic::Application for Window {
|
|||
},
|
||||
Some(Box::new(move |state: &Window| {
|
||||
let content_list = list_column()
|
||||
.padding(5)
|
||||
.spacing(0)
|
||||
.add(settings::item(
|
||||
"Example row",
|
||||
cosmic::widget::container(
|
||||
|
|
|
|||
2
iced
2
iced
|
|
@ -1 +1 @@
|
|||
Subproject commit d9fe129f7316cea3ef6d2576e9421a305759b8bf
|
||||
Subproject commit de1247123a631087d2f78831c99f5f1b66d5a582
|
||||
6
justfile
6
justfile
|
|
@ -23,12 +23,6 @@ check-json: (check '--message-format=json')
|
|||
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
|
||||
clean-dist: clean
|
||||
rm -rf .cargo vendor vendor.tar target
|
||||
|
|
|
|||
83
src/lib.rs
83
src/lib.rs
|
|
@ -5,89 +5,6 @@
|
|||
#![cfg_attr(target_os = "redox", feature(lazy_cell))]
|
||||
#![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.
|
||||
pub mod prelude {
|
||||
pub use crate::ApplicationExt;
|
||||
|
|
|
|||
|
|
@ -51,10 +51,9 @@ impl KeyBind {
|
|||
physical_key: Option<&Physical>,
|
||||
) -> bool {
|
||||
let key_eq = self.key_eq(key)
|
||||
|| (!is_latin_shortcut_key(key)
|
||||
&& physical_key
|
||||
.and_then(physical_key_to_latin)
|
||||
.is_some_and(|latin| self.key_eq(&latin)));
|
||||
|| physical_key
|
||||
.and_then(physical_key_to_latin)
|
||||
.is_some_and(|latin| self.key_eq(&latin));
|
||||
key_eq
|
||||
&& modifiers.logo() == self.modifiers.contains(&Modifier::Super)
|
||||
&& 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`.
|
||||
///
|
||||
/// 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)),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,7 @@ use iced::{Element, Event, Length, Radians, Rectangle, Renderer, Size, Vector, m
|
|||
use std::f32::consts::PI;
|
||||
use std::time::Duration;
|
||||
|
||||
const MIN_GAP_ANGLE: Radians = Radians(PI / 4.0);
|
||||
const MAX_WRAP: f32 = 1.0 - MIN_GAP_ANGLE.0 / (2.0 * PI);
|
||||
const MIN_ANGLE: Radians = Radians(PI / 8.0);
|
||||
|
||||
#[must_use]
|
||||
pub struct Circular<Theme>
|
||||
|
|
@ -77,6 +76,12 @@ where
|
|||
self.progress = Some(progress.clamp(0.0, 1.0));
|
||||
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>
|
||||
|
|
@ -143,12 +148,11 @@ where
|
|||
shell.request_redraw();
|
||||
}
|
||||
} else {
|
||||
state.animation = state.animation.timed_transition(
|
||||
self.cycle_duration,
|
||||
self.period,
|
||||
MAX_WRAP,
|
||||
*now,
|
||||
);
|
||||
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);
|
||||
state.cache.clear();
|
||||
shell.request_redraw();
|
||||
}
|
||||
|
|
@ -187,6 +191,25 @@ where
|
|||
|
||||
// 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 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 mut builder = canvas::path::Builder::new();
|
||||
builder.arc(canvas::path::Arc {
|
||||
|
|
@ -199,9 +222,10 @@ where
|
|||
&builder.build(),
|
||||
canvas::Stroke::default()
|
||||
.with_color(custom_style.bar_color)
|
||||
.with_width(self.bar_height)
|
||||
.with_line_cap(canvas::LineCap::Round),
|
||||
.with_width(self.bar_height),
|
||||
);
|
||||
draw_cap(frame, end, false);
|
||||
draw_cap(frame, start, true);
|
||||
};
|
||||
|
||||
if self.progress.is_some() {
|
||||
|
|
@ -219,11 +243,10 @@ where
|
|||
}
|
||||
draw_bar(frame, 0.0, state.progress.current);
|
||||
} else {
|
||||
// f32::EPSILON prevents flicker when wrap angle is 0.0
|
||||
let (start, end) =
|
||||
state
|
||||
.animation
|
||||
.bar_positions(self.cycle_duration, f32::EPSILON, MAX_WRAP);
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use super::animation::{Animation, Progress};
|
|||
use super::style::StyleSheet;
|
||||
use iced::advanced::widget::tree::{self, Tree};
|
||||
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;
|
||||
|
||||
|
|
@ -39,7 +39,7 @@ where
|
|||
period: Duration::from_secs(2),
|
||||
progress: None,
|
||||
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.
|
||||
/// 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 {
|
||||
let mut markers = markers.into();
|
||||
for marker in &mut markers {
|
||||
*marker = marker.clamp(0.0, 1.0);
|
||||
for bp in &mut markers {
|
||||
*bp = bp.clamp(0.0, 1.0);
|
||||
}
|
||||
markers.sort_by(f32::total_cmp);
|
||||
markers.dedup();
|
||||
|
|
@ -96,7 +96,7 @@ where
|
|||
|
||||
/// 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.max(1.0);
|
||||
self.segment_spacing = spacing.into().0;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
@ -197,144 +197,110 @@ where
|
|||
let border_color = custom_style.border_color.unwrap_or(custom_style.bar_color);
|
||||
let radius = custom_style.border_radius;
|
||||
|
||||
let mut draw_quad = |x: f32,
|
||||
width: f32,
|
||||
color: iced::Color,
|
||||
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::Quad {
|
||||
bounds: Rectangle {
|
||||
x: bounds.x + x,
|
||||
y: bounds.y + (bounds.height - height) / 2.0,
|
||||
width,
|
||||
height,
|
||||
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(
|
||||
renderer::Quad {
|
||||
bounds: Rectangle {
|
||||
x: bounds.x + x * bounds.width,
|
||||
y: bounds.y,
|
||||
width: width * bounds.width,
|
||||
height: bounds.height,
|
||||
},
|
||||
border,
|
||||
snap: true,
|
||||
..renderer::Quad::default()
|
||||
},
|
||||
border,
|
||||
snap: true,
|
||||
..renderer::Quad::default()
|
||||
},
|
||||
color,
|
||||
);
|
||||
Background::Color(color),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
if self.progress.is_some() {
|
||||
let current_p = state.progress.current;
|
||||
let len = self.markers.len();
|
||||
let spacing = self.segment_spacing;
|
||||
let spacing = self.segment_spacing.max(1.0);
|
||||
let radius_inner = radius.min(spacing);
|
||||
|
||||
let gap = if len != 0 {
|
||||
spacing / bounds.width
|
||||
} else {
|
||||
let gap = if self.markers.is_empty() {
|
||||
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;
|
||||
for i in 0..=len {
|
||||
let (seg_lo, r_left) = if i == 0 {
|
||||
(0.0, radius)
|
||||
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 - 1], radius_inner)
|
||||
self.markers[i]
|
||||
};
|
||||
let (seg_hi, r_right) = if i == len {
|
||||
(1.0, radius)
|
||||
(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 {
|
||||
(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_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(
|
||||
x_start * bounds.width,
|
||||
x_width * bounds.width,
|
||||
x_start,
|
||||
x_width,
|
||||
custom_style.track_color,
|
||||
iced::Border {
|
||||
width: border_width,
|
||||
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 {
|
||||
// draw track
|
||||
draw_quad(
|
||||
0.0,
|
||||
bounds.width,
|
||||
1.0,
|
||||
custom_style.track_color,
|
||||
iced::Border {
|
||||
width: border_width,
|
||||
color: border_color,
|
||||
radius: radius.into(),
|
||||
},
|
||||
true,
|
||||
bounds.width,
|
||||
);
|
||||
|
||||
// draw bar
|
||||
|
|
@ -346,26 +312,24 @@ where
|
|||
let start = bar_start % 1.0;
|
||||
let right_width = (1.0 - start).min(length);
|
||||
let left_width = length - right_width;
|
||||
let border = iced::Border {
|
||||
radius: radius.into(),
|
||||
..iced::Border::default()
|
||||
};
|
||||
|
||||
draw_quad(
|
||||
start * bounds.width,
|
||||
right_width * bounds.width,
|
||||
start,
|
||||
right_width,
|
||||
custom_style.bar_color,
|
||||
border,
|
||||
false,
|
||||
(right_width + start) * bounds.width,
|
||||
iced::Border {
|
||||
radius: radius.into(),
|
||||
..iced::Border::default()
|
||||
},
|
||||
);
|
||||
draw_quad(
|
||||
0.0,
|
||||
left_width * bounds.width,
|
||||
left_width,
|
||||
custom_style.bar_color,
|
||||
border,
|
||||
false,
|
||||
left_width * bounds.width,
|
||||
iced::Border {
|
||||
radius: radius.into(),
|
||||
..iced::Border::default()
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue