Skip to content

Commit

Permalink
Add timer example from 7gui (#284)
Browse files Browse the repository at this point in the history
  • Loading branch information
sudormrfbin authored Jan 21, 2024
1 parent 4dded85 commit 651b1de
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 0 deletions.
10 changes: 10 additions & 0 deletions examples/timer/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "timer"
edition = "2021"
license.workspace = true
version.workspace = true

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
floem = { path = "../.." }
18 changes: 18 additions & 0 deletions examples/timer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Timer

This is an example timer app, as described in
[task 4][task4] of [7gui tasks][7gui].

> Timer deals with concurrency in the sense that a timer process
> that updates the elapsed time runs concurrently to the user’s
> interactions with the GUI application. This also means that the
> solution to competing user and signal interactions is tested. The
> fact that slider adjustments must be reflected immediately moreover
> tests the responsiveness of the solution. A good solution will make
> it clear that the signal is a timer tick and, as always, has not
> much scaffolding.
![timer](https://github.com/lapce/floem/assets/23398472/b55dae4f-56fe-4e9f-a0ee-1898db048588)

[task4]: https://eugenkiss.github.io/7guis/tasks/#timer
[7gui]: https://eugenkiss.github.io/7guis/
91 changes: 91 additions & 0 deletions examples/timer/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
use std::time::{Duration, Instant};

use floem::{
action::exec_after,
reactive::{create_effect, create_rw_signal},
style::BorderRadius,
unit::UnitExt,
view::View,
views::{container, label, stack, text, v_stack, Decorators},
widgets::{button, slider},
};

fn main() {
floem::launch(app_view);
}

fn app_view() -> impl View {
// We take maximum duration as 100s for convenience so that
// one percent represents one second.
let target_duration = create_rw_signal(100.0);
let duration_slider = thin_slider(move || target_duration.get())
.on_change_pct(move |new| target_duration.set(new));

let elapsed_time = create_rw_signal(Duration::ZERO);
let is_active = move || elapsed_time.get().as_secs_f32() < target_duration.get();

let elapsed_time_label = label(move || {
format!(
"{:.1}s",
if is_active() {
elapsed_time.get().as_secs_f32()
} else {
target_duration.get()
}
)
});

let tick = create_rw_signal(());
create_effect(move |_| {
tick.track();
let before_exec = Instant::now();

exec_after(Duration::from_millis(100), move |_| {
if is_active() {
elapsed_time.update(|d| *d += before_exec.elapsed());
}
tick.set(());
});
});

let progress = move || elapsed_time.get().as_secs_f32() / target_duration.get() * 100.0;
let elapsed_time_bar = gauge(progress);

let reset_button = button(|| "Reset").on_click_stop(move |_| elapsed_time.set(Duration::ZERO));

let view = v_stack((
stack((text("Elapsed Time: "), elapsed_time_bar)).style(|s| s.justify_between()),
elapsed_time_label,
stack((text("Duration: "), duration_slider)).style(|s| s.justify_between()),
reset_button,
))
.style(|s| s.gap(5, 5));

container(view).style(|s| {
s.size(100.pct(), 100.pct())
.flex_col()
.items_center()
.justify_center()
})
}

/// A slider with a thin bar instead of the default thick bar.
fn thin_slider(fill_percent: impl Fn() -> f32 + 'static) -> slider::Slider {
slider::slider(fill_percent).style(|s| {
s.width(200)
.class(slider::AccentBarClass, |s| s.height(30.pct()))
.class(slider::BarClass, |s| s.height(30.pct()))
})
}

/// A non-interactive slider that has been repurposed into a progress bar.
fn gauge(fill_percent: impl Fn() -> f32 + 'static) -> slider::Slider {
slider::slider(fill_percent)
.disable_events(|| true)
.style(|s| {
s.width(200)
.set(slider::HandleRadius, 0.pct())
.class(slider::BarClass, |s| s.set(BorderRadius, 25.pct()))
.class(slider::AccentBarClass, |s| s.set(BorderRadius, 25.pct()))
})
}

0 comments on commit 651b1de

Please sign in to comment.