Skip to content

Commit

Permalink
feat(ui): self-serve ui for pearl orbs (#283)
Browse files Browse the repository at this point in the history
based on diamond ui
events for Pearl are forwarded to a different
handler depending on mode of operation
  • Loading branch information
fouge authored Nov 7, 2024
1 parent 084baee commit 149f014
Show file tree
Hide file tree
Showing 11 changed files with 1,191 additions and 370 deletions.
2 changes: 1 addition & 1 deletion ui/cone/examples/cone-simulation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ async fn simulation_task(cone: &mut Cone) -> eyre::Result<()> {
}
SimulationState::QrCode => {
for pixel in pixels.iter_mut() {
*pixel = Argb::DIAMOND_SHROUD_SUMMON_USER_AMBER;
*pixel = Argb::DIAMOND_CENTER_SUMMON_USER_AMBER;
}

let cmd =
Expand Down
63 changes: 50 additions & 13 deletions ui/rgb/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,37 @@ impl ops::Mul<f64> for Argb {

#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
fn mul(self, rhs: f64) -> Self::Output {
Argb(
self.0,
((f64::from(self.1) * rhs) as u8).clamp(0, u8::MAX),
((f64::from(self.2) * rhs) as u8).clamp(0, u8::MAX),
((f64::from(self.3) * rhs) as u8).clamp(0, u8::MAX),
)
// at low brightness (low rhs)
// prefer to turn LED off if the resulting color has only 1 component
// with an initial color that has more than 1 component
if ((self.1 != 0) as u8) + ((self.2 != 0) as u8) + ((self.3 != 0) as u8) > 1 {
let res = Argb(
self.0,
((f64::from(self.1) * rhs) as u8).clamp(0, u8::MAX),
((f64::from(self.2) * rhs) as u8).clamp(0, u8::MAX),
((f64::from(self.3) * rhs) as u8).clamp(0, u8::MAX),
);
// if result color has only 1 component, prefer to turn LED off
if ((res.1 != 0) as u8) + ((res.2 != 0) as u8) + ((res.3 != 0) as u8) == 1 {
Argb::OFF
} else {
res
}
} else {
Argb(
self.0,
((f64::from(self.1) * rhs) as u8).clamp(0, u8::MAX),
((f64::from(self.2) * rhs) as u8).clamp(0, u8::MAX),
((f64::from(self.3) * rhs) as u8).clamp(0, u8::MAX),
)
}
}
}

impl ops::MulAssign<f64> for Argb {
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
fn mul_assign(&mut self, rhs: f64) {
self.1 = ((f64::from(self.1) * rhs) as u8).clamp(0, u8::MAX);
self.2 = ((f64::from(self.2) * rhs) as u8).clamp(0, u8::MAX);
self.3 = ((f64::from(self.3) * rhs) as u8).clamp(0, u8::MAX);
*self = *self * rhs;
}
}

Expand Down Expand Up @@ -69,6 +85,29 @@ impl Argb {
pub const PEARL_USER_SIGNUP: Argb = Argb(None, 31, 31, 31);
pub const PEARL_USER_FLASH: Argb = Argb(None, 255, 255, 255);

/// ***** Self-serve colors *****
/// We intentionally don't include blue in most of the color scheme
/// because a sine wave with a low blue component doesn't look good:
/// whiter once wave is over, but darker during the wave.
///
/// Outer-ring color during operator QR scans
pub const PEARL_RING_OPERATOR_QR_SCAN: Argb = Argb(None, 20, 6, 0);
pub const PEARL_RING_OPERATOR_QR_SCAN_SPINNER: Argb = Argb(None, 25, 15, 9);
pub const PEARL_RING_OPERATOR_QR_SCAN_SPINNER_OPERATOR_BASED: Argb =
Argb(None, 25, 22, 5);
/// Outer-ring color during user QR scans
pub const PEARL_RING_USER_QR_SCAN: Argb = Argb(None, 30, 20, 0);
pub const PEARL_RING_USER_QR_SCAN_SPINNER: Argb = Argb(None, 28, 25, 10);
/// Shroud color to invite user to scan / reposition in front of the orb
pub const PEARL_CENTER_SUMMON_USER_AMBER: Argb = Argb(None, 30, 20, 0);
/// Shroud color during user scan/capture (in progress)
pub const PEARL_CENTER_USER_CAPTURE: Argb = Argb(None, 30, 20, 0);
/// Outer-ring color during user scan/capture (in progress)
pub const PEARL_RING_USER_CAPTURE: Argb = Argb(None, 30, 20, 0);
/// Error color for outer ring
pub const PEARL_RING_ERROR_SALMON: Argb = Argb(None, 24, 4, 0);

/// ***** Self-serve colors *****
pub const DIAMOND_OPERATOR_AMBER: Argb =
Argb(Some(Self::DIMMING_MAX_VALUE), 20, 16, 0);
// To help quickly distinguish dev vs prod software,
Expand All @@ -87,10 +126,8 @@ impl Argb {
/// Outer-ring color during user QR scans
pub const DIAMOND_RING_USER_QR_SCAN: Argb = Argb(Some(5), 120, 80, 4);
pub const DIAMOND_RING_USER_QR_SCAN_SPINNER: Argb = Argb(Some(10), 100, 90, 35);
/// Shroud color to invite user to scan / reposition in front of the orb
pub const DIAMOND_SHROUD_SUMMON_USER_AMBER: Argb = Argb(Some(3), 95, 40, 3);
/// Shroud color during user scan/capture (in progress)
pub const DIAMOND_SHROUD_USER_CAPTURE: Argb = Argb(Some(3), 118, 51, 3);
/// Shroud color to invite user to scan / reposition in front of the orb and capture
pub const DIAMOND_CENTER_SUMMON_USER_AMBER: Argb = Argb(Some(3), 95, 40, 3);
/// Outer-ring color during user scan/capture (in progress)
pub const DIAMOND_RING_USER_CAPTURE: Argb = Argb(Some(10), 120, 100, 4);
pub const DIAMOND_CONE_AMBER: Argb = Argb(Some(Self::DIMMING_MAX_VALUE), 25, 18, 1);
Expand Down
53 changes: 51 additions & 2 deletions ui/src/engine/animations/progress.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::engine::animations::{render_lines, LIGHT_BLEEDING_OFFSET_RAD};
use crate::engine::{Animation, AnimationState, RingFrame};
use crate::engine::{Animation, AnimationState, RingFrame, Transition};
use eyre::eyre;
use orb_rgb::Argb;
use std::{any::Any, f64::consts::PI};

Expand All @@ -19,6 +20,8 @@ pub struct Progress<const N: usize> {
/// once `progress` reached, maintain progress ring to set `progress` during `progress_duration`
progress_duration: Option<f64>,
pulse_angle: f64,
transition: Option<Transition>,
transition_time: f64,
pub(crate) shape: Shape<N>,
}

Expand All @@ -44,6 +47,8 @@ impl<const N: usize> Progress<N> {
progress: initial_progress,
progress_duration,
pulse_angle: PULSE_ANGLE_RAD,
transition: None,
transition_time: 0.0,
shape: Shape {
progress: 0.0,
phase: 0.0,
Expand Down Expand Up @@ -87,8 +92,35 @@ impl<const N: usize> Animation for Progress<N> {
dt: f64,
idle: bool,
) -> AnimationState {
let scaling_factor = match self.transition {
Some(Transition::ForceStop) => return AnimationState::Finished,
Some(Transition::StartDelay(duration)) => {
self.transition_time += dt;
if self.transition_time >= duration {
self.transition = None;
}
return AnimationState::Running;
}
Some(Transition::FadeOut(duration)) => {
self.transition_time += dt;
if self.transition_time >= duration {
return AnimationState::Finished;
}
(self.transition_time * PI / 2.0 / duration).cos()
}
Some(Transition::FadeIn(duration)) => {
self.transition_time += dt;
if self.transition_time >= duration {
self.transition = None;
}
(self.transition_time * PI / 2.0 / duration).sin()
}
_ => 1.0,
};

tracing::trace!("scaling: {scaling_factor}");
if !idle {
self.shape.render(frame, self.color);
self.shape.render(frame, self.color * scaling_factor);
}

self.shape.progress = self.shape.progress
Expand Down Expand Up @@ -127,6 +159,23 @@ impl<const N: usize> Animation for Progress<N> {
}
}
}

fn stop(&mut self, transition: Transition) -> eyre::Result<()> {
match transition {
Transition::PlayOnce | Transition::Shrink => {
return Err(eyre!(
"Transition {:?} not supported for static animation",
transition
));
}
t => {
self.transition = Some(t);
self.transition_time = 0.0;
}
}

Ok(())
}
}

impl<const N: usize> Shape<N> {
Expand Down
31 changes: 24 additions & 7 deletions ui/src/engine/animations/simple_spinner.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::engine::animations::Static;
use crate::engine::{
Animation, AnimationState, RingFrame, Transition, TransitionStatus,
PEARL_RING_LED_COUNT,
};
use eyre::eyre;
use orb_rgb::Argb;
Expand Down Expand Up @@ -92,11 +93,23 @@ impl<const N: usize> Animation for SimpleSpinner<N> {
}

self.phase = (self.phase + dt * self.speed) % (2.0 * PI);
// `N - led-index` with `led-index` increasing in {0,N}, because the LED strip goes anti-clockwise
// `N as f64 * 3.0 / 4.0` because the first LED is at 6 o'clock (3PI/2)
let progress = (N as f64 - (self.phase * N as f64 / (2.0 * PI))
+ N as f64 * 3.0 / 4.0)
% N as f64;
let (progress, led_spinner_count) = if N == PEARL_RING_LED_COUNT {
// Pearl
(
(self.phase * N as f64 / (2.0 * PI) + N as f64 / 4.0) % N as f64,
15,
)
} else {
// Diamond
// `N - led-index` with `led-index` increasing in {0,N}, because the LED strip goes anti-clockwise
// `N as f64 * 3.0 / 4.0` because the first LED is at 6 o'clock (3PI/2)
(
(N as f64 - (self.phase * N as f64 / (2.0 * PI))
+ N as f64 * 3.0 / 4.0)
% N as f64,
3,
)
};
let led_index = progress as usize;
let head_tail_scale = progress - led_index as f64;

Expand Down Expand Up @@ -154,9 +167,13 @@ impl<const N: usize> Animation for SimpleSpinner<N> {
as i32) as u8,
);
*led = c * scaling_factor;
} else if i == (led_index + 1) % N || i == (led_index + 2) % N {
} else if (((led_index + led_spinner_count) % N) < led_index
&& (i < ((led_index + led_spinner_count) % N) || i > led_index))
|| (((led_index + led_spinner_count) % N) > led_index
&& (i < ((led_index + led_spinner_count) % N) && i > led_index))
{
*led = self.color * scaling_factor;
} else if i == (led_index + 3) % N {
} else if i == (led_index + led_spinner_count) % N {
let c = Argb(
self.color.0,
(background.1 as i32
Expand Down
3 changes: 2 additions & 1 deletion ui/src/engine/animations/wave.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ impl<const N: usize> Animation for Wave<N> {

intensity *= scaling_factor;

// specific case for pearl center
if N == PEARL_CENTER_LED_COUNT {
let r = f64::from(self.color.1) * intensity;
let g = f64::from(self.color.2) * intensity;
Expand Down Expand Up @@ -167,7 +168,7 @@ impl<const N: usize> Animation for Wave<N> {
*led = Argb(None, r, g, b);
}
} else {
// diamond
// pearl's ring or diamond
for led in frame.iter_mut() {
*led = self.color * intensity;
}
Expand Down
20 changes: 12 additions & 8 deletions ui/src/engine/diamond.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ use tokio_stream::wrappers::{IntervalStream, UnboundedReceiverStream};
use crate::engine::animations::alert::BlinkDurations;
use crate::engine::{
animations, operator, Animation, AnimationsStack, CenterFrame, ConeFrame, Event,
EventHandler, OperatorFrame, OrbType, QrScanSchema, QrScanUnexpectedReason,
RingFrame, Runner, RunningAnimation, SignupFailReason, Transition,
DIAMOND_CENTER_LED_COUNT, DIAMOND_CONE_LED_COUNT, DIAMOND_RING_LED_COUNT,
LED_ENGINE_FPS, LEVEL_BACKGROUND, LEVEL_FOREGROUND, LEVEL_NOTICE,
EventHandler, OperatingMode, OperatorFrame, OrbType, QrScanSchema,
QrScanUnexpectedReason, RingFrame, Runner, RunningAnimation, SignupFailReason,
Transition, DIAMOND_CENTER_LED_COUNT, DIAMOND_CONE_LED_COUNT,
DIAMOND_RING_LED_COUNT, LED_ENGINE_FPS, LEVEL_BACKGROUND, LEVEL_FOREGROUND,
LEVEL_NOTICE,
};
use crate::sound;
use crate::sound::Player;
Expand Down Expand Up @@ -181,6 +182,7 @@ impl Runner<DIAMOND_RING_LED_COUNT, DIAMOND_CENTER_LED_COUNT> {
is_api_mode: false,
paused: false,
gimbal: None,
operating_mode: OperatingMode::default(),
}
}

Expand Down Expand Up @@ -546,7 +548,7 @@ impl EventHandler for Runner<DIAMOND_RING_LED_COUNT, DIAMOND_CENTER_LED_COUNT> {
self.set_center(
LEVEL_FOREGROUND,
animations::Wave::<DIAMOND_CENTER_LED_COUNT>::new(
Argb::DIAMOND_SHROUD_SUMMON_USER_AMBER,
Argb::DIAMOND_CENTER_SUMMON_USER_AMBER,
3.0,
0.0,
true,
Expand Down Expand Up @@ -642,7 +644,7 @@ impl EventHandler for Runner<DIAMOND_RING_LED_COUNT, DIAMOND_CENTER_LED_COUNT> {
self.set_center(
LEVEL_FOREGROUND,
animations::Static::<DIAMOND_CENTER_LED_COUNT>::new(
Argb::DIAMOND_SHROUD_SUMMON_USER_AMBER,
Argb::DIAMOND_CENTER_SUMMON_USER_AMBER,
None,
)
.fade_in(1.5),
Expand Down Expand Up @@ -802,7 +804,6 @@ impl EventHandler for Runner<DIAMOND_RING_LED_COUNT, DIAMOND_CENTER_LED_COUNT> {

self.operator_signup_phase.signup_successful();

// ring, run error animation at NOTICE level, off for the rest.
self.set_ring(
LEVEL_BACKGROUND,
animations::Static::<DIAMOND_RING_LED_COUNT>::new(Argb::OFF, None),
Expand Down Expand Up @@ -891,7 +892,7 @@ impl EventHandler for Runner<DIAMOND_RING_LED_COUNT, DIAMOND_CENTER_LED_COUNT> {
self.set_ring(
LEVEL_NOTICE,
animations::Spinner::<DIAMOND_RING_LED_COUNT>::triple(
Argb::DIAMOND_SHROUD_SUMMON_USER_AMBER,
Argb::DIAMOND_RING_ERROR_SALMON,
None,
),
);
Expand Down Expand Up @@ -936,6 +937,9 @@ impl EventHandler for Runner<DIAMOND_RING_LED_COUNT, DIAMOND_CENTER_LED_COUNT> {
Event::Gimbal { x, y } => {
self.gimbal = Some((*x, *y));
}
Event::Flow { mode } => {
self.operating_mode = *mode;
}
}
Ok(())
}
Expand Down
Loading

0 comments on commit 149f014

Please sign in to comment.