Skip to content

Commit

Permalink
Merge pull request #1003 from futurepaul/spinner-widget
Browse files Browse the repository at this point in the history
Spinner widget
  • Loading branch information
futurepaul authored Jun 1, 2020
2 parents 9760cd7 + d33e04f commit a63eba8
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 4 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ This means that druid no longer requires cairo on macOS and uses Core Graphics i
- `Button::dynamic` constructor. ([#963] by [@totsteps])
- `set_menu` method on `UpdateCtx` and `LifeCycleCtx` ([#970] by [@cmyr])
- Standardize and expose more methods on more contexts ([#972] by [@cmyr])
- `Spinner` widget to represent loading states. ([#1003] by [@futurepaul])

### Changed

Expand Down Expand Up @@ -256,6 +257,7 @@ This means that druid no longer requires cairo on macOS and uses Core Graphics i
[#996]: https://github.com/xi-editor/druid/pull/996
[#997]: https://github.com/xi-editor/druid/pull/997
[#1001]: https://github.com/xi-editor/druid/pull/1001
[#1003]: https://github.com/xi-editor/druid/pull/1003

## [0.5.0] - 2020-04-01

Expand Down
8 changes: 4 additions & 4 deletions druid/examples/blocking_function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use druid::{
Selector, Target, Widget, WidgetExt, WindowDesc,
};

use druid::widget::{Button, Either, Flex, Label};
use druid::widget::{Button, Either, Flex, Label, Spinner};

const START_SLOW_FUNCTION: Selector<u32> = Selector::new("start_slow_function");

Expand Down Expand Up @@ -80,9 +80,9 @@ fn ui_builder() -> impl Widget<AppState> {
ctx.submit_command(cmd, None);
})
.padding(5.0);
let button_placeholder = Label::new(LocalizedString::new("Processing..."))
.padding(5.0)
.center();
let button_placeholder = Flex::column()
.with_child(Label::new(LocalizedString::new("Processing...")).padding(5.0))
.with_child(Spinner::new());

let text = LocalizedString::new("hello-counter")
.with_arg("count", |data: &AppState, _env| (data.value).into());
Expand Down
2 changes: 2 additions & 0 deletions druid/src/widget/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ mod radio;
mod scroll;
mod sized_box;
mod slider;
mod spinner;
mod split;
mod stepper;
#[cfg(feature = "svg")]
Expand Down Expand Up @@ -75,6 +76,7 @@ pub use radio::{Radio, RadioGroup};
pub use scroll::Scroll;
pub use sized_box::SizedBox;
pub use slider::Slider;
pub use spinner::Spinner;
pub use split::Split;
pub use stepper::Stepper;
#[cfg(feature = "svg")]
Expand Down
130 changes: 130 additions & 0 deletions druid/src/widget/spinner.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// Copyright 2020 The xi-editor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! An animated spinner widget.
use std::f64::consts::PI;

use druid::kurbo::Line;
use druid::widget::prelude::*;
use druid::{theme, Color, Data, KeyOrValue, Point, Vec2};

/// An animated spinner widget for showing a loading state.
///
/// To customize the spinner's size, you can place it inside a [`SizedBox`]
/// that has a fixed width and height.
///
/// [`SizedBox`]: struct.SizedBox.html
pub struct Spinner {
t: f64,
color: KeyOrValue<Color>,
}

impl Spinner {
/// Create a spinner widget
pub fn new() -> Spinner {
Spinner::default()
}

/// Builder-style method for setting the spinner's color.
///
/// The argument can be either a `Color` or a [`Key<Color>`].
///
/// [`Key<Color>`]: ../struct.Key.html
pub fn with_color(mut self, color: impl Into<KeyOrValue<Color>>) -> Self {
self.color = color.into();
self
}

/// Set the spinner's color.
///
/// The argument can be either a `Color` or a [`Key<Color>`].
///
/// [`Key<Color>`]: ../struct.Key.html
pub fn set_color(&mut self, color: impl Into<KeyOrValue<Color>>) {
self.color = color.into();
}
}

impl Default for Spinner {
fn default() -> Self {
Spinner {
t: 0.0,
color: theme::LABEL_COLOR.into(),
}
}
}

impl<T: Data> Widget<T> for Spinner {
fn event(&mut self, _ctx: &mut EventCtx, _event: &Event, _data: &mut T, _env: &Env) {}

fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, _data: &T, _env: &Env) {
if let LifeCycle::WidgetAdded = event {
ctx.request_anim_frame();
}

if let LifeCycle::AnimFrame(interval) = event {
self.t += (*interval as f64) * 1e-9;
if self.t >= 1.0 {
self.t = 0.0;
}
ctx.request_anim_frame();
}
}

fn update(&mut self, _ctx: &mut UpdateCtx, _old_data: &T, _data: &T, _env: &Env) {}

fn layout(
&mut self,
_layout_ctx: &mut LayoutCtx,
bc: &BoxConstraints,
_data: &T,
env: &Env,
) -> Size {
bc.debug_check("Spinner");

if bc.is_width_bounded() && bc.is_height_bounded() {
bc.max()
} else {
bc.constrain(Size::new(
env.get(theme::BASIC_WIDGET_HEIGHT),
env.get(theme::BASIC_WIDGET_HEIGHT),
))
}
}

fn paint(&mut self, ctx: &mut PaintCtx, _data: &T, env: &Env) {
let t = self.t;
let (width, height) = (ctx.size().width, ctx.size().height);
let center = Point::new(width / 2.0, height / 2.0);
let (r, g, b, original_alpha) = Color::as_rgba(&self.color.resolve(env));
let scale_factor = width.min(height) / 40.0;

for step in 1..=12 {
let step = f64::from(step);
let fade_t = (t * 12.0 + 1.0).trunc();
let fade = ((fade_t + step).rem_euclid(12.0) / 12.0) + 1.0 / 12.0;
let angle = Vec2::from_angle((step / 12.0) * -2.0 * PI);
let ambit_start = center + (10.0 * scale_factor * angle);
let ambit_end = center + (20.0 * scale_factor * angle);
let color = Color::rgba(r, g, b, fade * original_alpha);

ctx.stroke(
Line::new(ambit_start, ambit_end),
&color,
3.0 * scale_factor,
);
}
}
}

0 comments on commit a63eba8

Please sign in to comment.