improv(circular): prevent caps from touching
Some checks failed
Continuous Integration / format (push) Has been cancelled
Continuous Integration / tests (--no-default-features --features "") (push) Has been cancelled
Continuous Integration / tests (--no-default-features --features "applet") (push) Has been cancelled
Continuous Integration / tests (--no-default-features --features "desktop,smol") (push) Has been cancelled
Continuous Integration / tests (--no-default-features --features "desktop,tokio") (push) Has been cancelled
Continuous Integration / tests (--no-default-features --features "wayland") (push) Has been cancelled
Pages / pages (push) Has been cancelled
Continuous Integration / tests (--no-default-features --features "winit") (push) Has been cancelled
Continuous Integration / tests (--no-default-features --features "winit_debug") (push) Has been cancelled
Continuous Integration / tests (--no-default-features --features "winit_tokio") (push) Has been cancelled
Continuous Integration / tests (--no-default-features --features "winit_wgpu") (push) Has been cancelled
Continuous Integration / tests (-p cosmic-theme) (push) Has been cancelled
Continuous Integration / examples (application) (push) Has been cancelled
Continuous Integration / examples (context-menu) (push) Has been cancelled
Continuous Integration / examples (nav-context) (push) Has been cancelled
Continuous Integration / examples (open-dialog) (push) Has been cancelled

This commit is contained in:
Vukašin Vojinović 2026-04-18 16:33:57 +02:00 committed by Ashley Wulber
parent c423ad1bfc
commit 95756b1a57
3 changed files with 36 additions and 25 deletions

View file

@ -21,4 +21,5 @@ features = [
"single-instance", "single-instance",
"surface-message", "surface-message",
"multi-window", "multi-window",
"wgpu",
] ]

View file

@ -200,7 +200,7 @@ impl cosmic::Application for App {
.map_or("No page selected", String::as_str); .map_or("No page selected", String::as_str);
let centered = widget::container( let centered = widget::container(
widget::column::with_capacity(5) widget::column::with_capacity(14)
.push(widget::text::body(page_content)) .push(widget::text::body(page_content))
.push( .push(
widget::text_input::text_input("", &self.input_1) widget::text_input::text_input("", &self.input_1)
@ -223,6 +223,7 @@ impl cosmic::Application for App {
.on_clear(Message::Ignore), .on_clear(Message::Ignore),
) )
.push(widget::progress_bar::circular::Circular::new().size(50.0)) .push(widget::progress_bar::circular::Circular::new().size(50.0))
.push(widget::progress_bar::circular::Circular::new().size(20.0))
.push( .push(
widget::progress_bar::linear::Linear::new() widget::progress_bar::linear::Linear::new()
.girth(10.0) .girth(10.0)

View file

@ -15,8 +15,6 @@ use std::f32::consts::PI;
use std::time::Duration; use std::time::Duration;
const MIN_ANGLE: Radians = Radians(PI / 8.0); const MIN_ANGLE: Radians = Radians(PI / 8.0);
const WRAP_ANGLE: Radians = Radians(2.0 * PI - PI / 4.0);
const BASE_ROTATION_SPEED: u32 = u32::MAX / 80;
#[must_use] #[must_use]
pub struct Circular<Theme> pub struct Circular<Theme>
@ -83,6 +81,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_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 - gap * 2.0)
}
} }
impl<Theme> Default for Circular<Theme> impl<Theme> Default for Circular<Theme>
@ -122,7 +126,7 @@ impl Default for Animation {
} }
impl Animation { impl Animation {
fn next(&self, additional_rotation: u32, now: Instant) -> Self { fn next(&self, additional_rotation: u32, wrap_angle: f32, now: Instant) -> Self {
match self { match self {
Self::Expanding { rotation, .. } => Self::Contracting { Self::Expanding { rotation, .. } => Self::Contracting {
start: now, start: now,
@ -133,9 +137,9 @@ impl Animation {
Self::Contracting { rotation, .. } => Self::Expanding { Self::Contracting { rotation, .. } => Self::Expanding {
start: now, start: now,
progress: 0.0, progress: 0.0,
rotation: rotation.wrapping_add(BASE_ROTATION_SPEED.wrapping_add( rotation: rotation.wrapping_add(
(f64::from(WRAP_ANGLE / (2.0 * Radians::PI)) * f64::from(u32::MAX)) as u32, (f64::from((wrap_angle) / (2.0 * PI)) * f64::from(u32::MAX)) as u32,
)), ),
last: now, last: now,
}, },
} }
@ -157,6 +161,7 @@ impl Animation {
&self, &self,
cycle_duration: Duration, cycle_duration: Duration,
rotation_duration: Duration, rotation_duration: Duration,
wrap_angle: f32,
now: Instant, now: Instant,
) -> Self { ) -> Self {
let elapsed = now.duration_since(self.start()); let elapsed = now.duration_since(self.start());
@ -165,7 +170,7 @@ impl Animation {
* (u32::MAX) as f32) as u32; * (u32::MAX) as f32) as u32;
match elapsed { match elapsed {
elapsed if elapsed > cycle_duration => self.next(additional_rotation, now), elapsed if elapsed > cycle_duration => self.next(additional_rotation, wrap_angle, now),
_ => self.with_elapsed(cycle_duration, additional_rotation, elapsed, now), _ => self.with_elapsed(cycle_duration, additional_rotation, elapsed, now),
} }
} }
@ -267,10 +272,13 @@ where
return; return;
} }
if let Event::Window(window::Event::RedrawRequested(now)) = event { if let Event::Window(window::Event::RedrawRequested(now)) = event {
state.animation = let (_, wrap_angle) = self.min_wrap_angle(self.size / 2.0 - self.bar_height);
state state.animation = state.animation.timed_transition(
.animation self.cycle_duration,
.timed_transition(self.cycle_duration, self.rotation_duration, *now); self.rotation_duration,
wrap_angle,
*now,
);
state.cache.clear(); state.cache.clear();
shell.request_redraw(); shell.request_redraw();
@ -380,22 +388,23 @@ where
} else { } else {
let mut builder = canvas::path::Builder::new(); let mut builder = canvas::path::Builder::new();
let start = Radians(state.animation.rotation() * 2.0 * PI); 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 { let (start_angle, end_angle) = match state.animation {
Animation::Expanding { progress, .. } => ( Animation::Expanding { progress, .. } => (
start, start,
start + MIN_ANGLE + WRAP_ANGLE * (smootherstep(progress)), start + min_angle + wrap_angle * smootherstep(progress),
), ),
Animation::Contracting { progress, .. } => ( Animation::Contracting { progress, .. } => (
start + WRAP_ANGLE * (smootherstep(progress)), start + wrap_angle * smootherstep(progress),
start + MIN_ANGLE + WRAP_ANGLE, start + min_angle + wrap_angle,
), ),
}; };
builder.arc(canvas::path::Arc { builder.arc(canvas::path::Arc {
center: frame.center(), center: frame.center(),
radius: track_radius, radius: track_radius,
start_angle, start_angle: Radians(start_angle),
end_angle, end_angle: Radians(end_angle),
}); });
let bar_path = builder.build(); let bar_path = builder.build();
@ -410,23 +419,23 @@ where
let mut builder = canvas::path::Builder::new(); let mut builder = canvas::path::Builder::new();
// get center of end of arc for rounded cap // get center of end of arc for rounded cap
let end_center = frame.center() let end_center =
+ Vector::new(end_angle.0.cos(), end_angle.0.sin()) * track_radius; frame.center() + Vector::new(end_angle.cos(), end_angle.sin()) * track_radius;
builder.arc(canvas::path::Arc { builder.arc(canvas::path::Arc {
center: end_center, center: end_center,
radius: self.bar_height / 2.0, radius: self.bar_height / 2.0,
start_angle: Radians(end_angle.0), start_angle: Radians(end_angle),
end_angle: Radians(end_angle.0 + PI), end_angle: Radians(end_angle + PI),
}); });
// get center of start of arc for rounded cap // get center of start of arc for rounded cap
let start_center = frame.center() let start_center = frame.center()
+ Vector::new(start_angle.0.cos(), start_angle.0.sin()) * track_radius; + Vector::new(start_angle.cos(), start_angle.sin()) * track_radius;
builder.arc(canvas::path::Arc { builder.arc(canvas::path::Arc {
center: start_center, center: start_center,
radius: self.bar_height / 2.0, radius: self.bar_height / 2.0,
start_angle: Radians(start_angle.0 - PI), start_angle: Radians(start_angle - PI),
end_angle: Radians(start_angle.0), end_angle: Radians(start_angle),
}); });
let cap_path = builder.build(); let cap_path = builder.build();