Repository: swimos/swim-rust (language: rust)

Issue:
Port ripple demo



Discussion / hints:

Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index 11ec76b40..8499ea7d0 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -40,7 +40,7 @@ members = [
     "example_apps/join_value",
     "example_apps/aggregations",
     "example_apps/time_series",
-    "example_apps/devguide",
+    "example_apps/devguide", "example_apps/ripple",
 ]
 
 [workspace.package]
diff --git a/README.md b/README.md
index 1139e4831..9d912808e 100644
--- a/README.md
+++ b/README.md
@@ -22,12 +22,15 @@ host can share a single web-socket connection.
 [![SwimOS Form Crates.io Version][swimos-form-badge]][swimos-form-crate]
 
 [swimos-badge]: https://img.shields.io/crates/v/swimos?label=swimos
+
 [swimos-crate]: https://crates.io/crates/swimos
 
 [swimos-form-badge]: https://img.shields.io/crates/v/swimos?label=swimos_form
+
 [swimos-form-crate]: https://crates.io/crates/swimos_form
 
 [swimos-client-badge]: https://img.shields.io/crates/v/swimos?label=swimos_client
+
 [swimos-client-crate]: https://crates.io/crates/swimos_client
 
 [Website](https://swimos.org/) | [Developer Guide](https://www.swimos.org/server/rust/developer-guide/) | [Server API Docs](https://docs.rs/swimos/latest/swimos/) | [Client API Docs](https://docs.rs/swimos_client/latest/swimos_client/)
@@ -123,6 +126,9 @@ lane will then be set to `5` and the following will be printed on the console:
 Received value: 5 for 'lane' on agent at URI: /examples/name.
 ```
 
+A number of example applications are available in the [example_apps](example_apps) directory which demonstrate
+individual features as well as more comprehensive applications.
+
 ## Development
 
 See the [development guide](DEVELOPMENT.md).
diff --git a/example_apps/README.md b/example_apps/README.md
index 12789b9f4..118bfe4bf 100644
--- a/example_apps/README.md
+++ b/example_apps/README.md
@@ -19,6 +19,7 @@ binaries for their corresponding client applications. Each directory contains de
 - [map_lane_persistence](map_lane_persistence): Application demonstrating Map Lanes with persistence.
 - [map_store](map_store): Application demonstrating Map Stores.
 - [map_store_persistence](map_store_persistence): Application demonstrating Map Stores with persistence.
+- [ripple](ripple): Real-time synchronous shared multiplayer experience.
 - [supply_lane](supply_lane): Application demonstrating Supply Lanes.
 - [time_series](time_series): Reference code for the [Time Series](https://www.swimos.org/server/rust/time-series/)
   guide.
diff --git a/example_apps/ripple/Cargo.toml b/example_apps/ripple/Cargo.toml
new file mode 100644
index 000000000..29a29ea76
--- /dev/null
+++ b/example_apps/ripple/Cargo.toml
@@ -0,0 +1,22 @@
+[package]
+name = "ripple"
+version.workspace = true
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+homepage.workspace = true
+publish = false
+
+[dependencies]
+swimos = { workspace = true, features = ["server", "agent"] }
+swimos_form = { workspace = true }
+example-util = { path = "../example_util" }
+swimos_utilities = { workspace = true, features = ["trigger"] }
+axum = { workspace = true, features = ["tokio", "http1"] }
+tracing = { workspace = true }
+tracing-subscriber = { workspace = true, features = ["env-filter"] }
+futures = { workspace = true }
+tokio = { workspace = true, features = ["rt-multi-thread", "macros", "sync", "io-util", "fs"] }
+tokio-util = { workspace = true }
+rand = { workspace = true }
+chrono = { workspace = true }
\ No newline at end of file
diff --git a/example_apps/ripple/README.md b/example_apps/ripple/README.md
new file mode 100644
index 000000000..cc4dfe0b3
--- /dev/null
+++ b/example_apps/ripple/README.md
@@ -0,0 +1,30 @@
+# Ripple
+
+![Ripple](assets/ripple.png "Ripple")
+
+Ripple is a real-time synchronous shared multiplayer experience built on the Swim platform.
+
+See a hosted version of this app [here](https://ripple.swim.inc).
+
+## Usage
+
+Run the application using:
+
+```shell
+$ cargo run --bin ripple
+```
+
+Internally, this will spawn two servers: the Swim server which is running at localhost:9001 and a UI server running
+at http://localhost:9002. Using your browser, navigate to http://localhost:9002/index.html and randomly click
+on the screen to generate 'ripples'. These ripples are propagated to all users which are connected to the Swim server.
+You may also hold your mouse's left button down to generate a 'charge' which is denoted by a larger circle displayed on
+the screen.
+
+This example application demonstrates a number of core Swim features:
+
+- Value Lanes: To propagate the latest ripple.
+- Map Lanes: To propagate the current charges that are happening.
+- Command Lanes: For publishing new ripples and charges.
+- Custom Event Types: Structures and Enumerations are used by Command Lanes for mutating the state of lanes.
+- Event Handlers: Event Handlers are scheduled to run at random intervals to generate new ripples and prune any old
+  charges that were not automatically removed if a user's browser suddenly closed.
\ No newline at end of file
diff --git a/example_apps/ripple/assets/ripple.png b/example_apps/ripple/assets/ripple.png
new file mode 100644
index 000000000..3c1151d0e
Binary files /dev/null and b/example_apps/ripple/assets/ripple.png differ
diff --git a/example_apps/ripple/src/agent.rs b/example_apps/ripple/src/agent.rs
new file mode 100644
index 000000000..9fc07ee32
--- /dev/null
+++ b/example_apps/ripple/src/agent.rs
@@ -0,0 +1,383 @@
+// Copyright 2015-2024 Swim Inc.
+//
+// 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.
+
+use std::collections::HashMap;
+use std::fmt::{Display, Formatter};
+use std::time::Duration;
+
+use chrono::Utc;
+use futures::stream::unfold;
+use futures::StreamExt;
+use rand::rngs::ThreadRng;
+use rand::seq::SliceRandom;
+use rand::Rng;
+use swimos::agent::agent_lifecycle::HandlerContext;
+use swimos::agent::event_handler::{EventHandler, HandlerActionExt, Sequentially};
+use swimos::agent::lanes::{CommandLane, MapLane, ValueLane};
+use swimos::agent::{lifecycle, projections, AgentLaneModel};
+use swimos::model::Timestamp;
+
+use swimos_form::Form;
+use tokio::time::sleep;
+use tracing::{info, trace};
+
+const TAG: &str = "swim0";
+
+/// Ripple identifier.
+#[derive(Clone, Debug, Form, Eq, Hash, PartialEq, PartialOrd, Ord)]
+pub enum Id {
+    /// System-generated event.
+    System(String),
+    /// User-generated event. Tuple of a base64 encoded byte array and a string containing "mouse".
+    User(String, String),
+}
+
+/// Ripple configuration.
+#[derive(Copy, Clone, Debug, Form)]
+#[form(fields_convention = "camel")]
+pub struct Config {
+    /// The minimum number of ripples to generate for an event.
+    min_phases: usize,
+    /// The maximum number of ripples to generate for an event.
+    max_phases: usize,
+    /// The maximum time that a ripple should stay in the UI.
+    ripple_duration: usize,
+    /// Ripple ease time.
+    ripple_spread: usize,
+}
+
+impl Config {
+    /// Default minimum number of phases to generate for a ripple.
+    pub const MIN_PHASES: usize = 2;
+    /// Default maximum number of phases to generate for a ripple.
+    pub const MAX_PHASES: usize = 5;
+    /// Minimum delay to wait between generating ripples.
+    pub const MIN_DELAY: u64 = 500;
+    /// Maximum delay to wait between generating ripples.
+    pub const MAX_DELAY: u64 = 2000;
+}
+
+impl Default for Config {
+    fn default() -> Self {
+        Config {
+            min_phases: Self::MIN_PHASES,
+            max_phases: Self::MAX_PHASES,
+            ripple_duration: 5000,
+            ripple_spread: 3000,
+        }
+    }
+}
+
+/// Web Agent for mirroring ripples.
+#[derive(AgentLaneModel)]
+#[projections]
+pub struct MirrorAgent {
+    /// The current configuration for the universe.
+    mode: ValueLane<Config>,
+    /// Lane for receiving commands to publish new ripples.
+    ripple: CommandLane<Ripple>,
+    /// Lane for propagating the most recent ripple.
+    ripples: ValueLane<Ripple>,
+    /// Lane for receiving commands to publish new charges.
+    charge: CommandLane<ChargeAction>,
+    /// Lane for propagating charges.
+    charges: MapLane<Id, Charge>,
+}
+
+/// A charge model.
+///
+/// A charge is an event generated when a user is holding their left mouse button down and moving it
+/// around the screen.
+#[derive(Debug, Form, Clone)]
+pub struct Charge {
+    /// The ID of the user which is generating the charge.
+    id: Id,
+    /// The charge's current X position.
+    x: f64,
+    /// The charge's current Y position.
+    y: f64,
+    /// The color of the charge.
+    color: String,
+    /// The radius of the charge.
+    #[form(name = "r")]
+    radius: i32,
+    /// The time which the charge was created.
+    #[form(name = "t0")]
+    created: Timestamp,
+    /// The time which the charge was last updated.
+    #[form(name = "t")]
+    updated: Timestamp,
+}
+
+/// A charge event action.
+#[derive(Debug, Form, Clone)]
+pub enum ChargeAction {
+    /// Create a new charge event.
+    Create {
+        /// The ID of the user which is generating the charge.
+        id: Id,
+        /// The charge's current X position.
+        x: f64,
+        /// The charge's current Y position.
+        y: f64,
+        /// The color of the charge.
+        color: String,
+        /// The radius of the charge.
+        #[form(name = "r")]
+        radius: i32,
+    },
+    /// Move the charge to the new position. Updating the last updated timestamp to ensure that it
+    /// is not pruned.
+    Move {
+        /// The ID of the user which is generating the charge.
+        id: Id,
+        /// The charge's current X position.
+        x: f64,
+        /// The charge's current Y position.
+        y: f64,
+        /// The color of the charge.
+        color: String,
+        /// The radius of the charge.
+        #[form(name = "r")]
+        radius: i32,
+    },
+    /// Remove the charge.
+    Remove {
+        /// The ID of the charge to remove.
+        id: Id,
+    },
+}
+
+/// The agent's lifecycle.
+#[derive(Clone)]
+pub struct MirrorLifecycle;
+
+#[lifecycle(MirrorAgent)]
+impl MirrorLifecycle {
+    /// Lifecycle event handler which is invoked exactly once when the agent starts.
+    ///
+    /// The handler will spawn a stream which will generate random ripples and prune charges.
+    #[on_start]
+    pub fn on_start(&self, context: HandlerContext<MirrorAgent>) -> impl EventHandler<MirrorAgent> {
+        let stream = unfold(Duration::default(), move |delay| async move {
+            sleep(delay).await;
+
+            let mut rng = ThreadRng::default();
+            let handler = generate_ripple(context).followed_by(cleanup_charges(context));
+            let next_delay =
+                Duration::from_millis(rng.gen_range(Config::MIN_DELAY..=Config::MAX_DELAY));
+
+            Some((handler, next_delay))
+        });
+        context
+            .get_agent_uri()
+            .and_then(move |uri| context.effect(move || info!(%uri, "Started agent")))
+            .followed_by(context.suspend_schedule(stream.boxed()))
+    }
+
+    /// Lifecycle event handler which is invoked exactly once when a new ripple has been received by
+    /// the "ripple" lane. This handler will set the state of the "ripples" lane to the received
+    /// `ripple`.
+    #[on_command(ripple)]
+    pub fn on_ripple(
+        &self,
+        context: HandlerContext<MirrorAgent>,
+        ripple: &Ripple,
+    ) -> impl EventHandler<MirrorAgent> {
+        trace!(?ripple, "New ripple");
+        context.set_value(MirrorAgent::RIPPLES, ripple.clone())
+    }
+
+    /// Lifecycle event handler which is invoked exactly once when a new charge action has been
+    /// received by the "charge" lane.
+    #[on_command(charge)]
+    pub fn on_charge_action(
+        &self,
+        context: HandlerContext<MirrorAgent>,
+        action: &ChargeAction,
+    ) -> impl EventHandler<MirrorAgent> {
+        trace!(?action, "New charge action");
+        match action.clone() {
+            ChargeAction::Create {
+                id,
+                x,
+                y,
+                color,
+                radius,
+            } => {
+                // Create a new charge
+                context
+                    .update(
+                        MirrorAgent::CHARGES,
+                        id.clone(),
+                        Charge {
+                            id,
+                            x,
+                            y,
+                            color,
+                            radius,
+                            created: Timestamp::now(),
+                            updated: Timestamp::now(),
+                        },
+                    )
+                    .boxed()
+            }
+            ChargeAction::Move {
+                id,
+                x,
+                y,
+                color,
+                radius,
+            } => {
+                // A 'charge' has moved, so we need to update its current state.
+                context
+                    .get_entry(MirrorAgent::CHARGES, id.clone())
+                    .and_then(move |entry: Option<Charge>| {
+                        let handler = match entry {
+                            Some(charge) => {
+                                let Charge { id, created, .. } = charge;
+                                let updated = Charge {
+                                    id: id.clone(),
+                                    x,
+                                    y,
+                                    color,
+                                    radius,
+                                    created,
+                                    // Update the timestamp to ensure that this charge isn't pruned
+                                    // by the 'cleanup_charges` function.
+                                    updated: Timestamp::now(),
+                                };
+                                Some(context.update(MirrorAgent::CHARGES, id, updated))
+                            }
+                            None => None,
+                        };
+                        handler.discard()
+                    })
+                    .boxed()
+            }
+            ChargeAction::Remove { id } => {
+                // A charge has ended so remove the corresponding entry.
+                context.remove(MirrorAgent::CHARGES, id).boxed()
+            }
+        }
+    }
+}
+
+/// A model of a ripple.
+#[derive(Debug, Form, Clone)]
+pub struct Ripple {
+    /// The ID of the user which is generated the ripple.
+    id: Id,
+    /// The ripple's X position.
+    x: f64,
+    /// The ripple's Y position.
+    y: f64,
+    /// Phases are the subsequent ripples that are generated by a ripple.
+    phases: Vec<f64>,
+    /// The color of the charge.
+    color: String,
+}
+
+impl Default for Ripple {
+    fn default() -> Self {
+        Ripple::random(Config::MIN_PHASES, Config::MAX_PHASES)
+    }
+}
+
+impl Ripple {
+    /// Generate a random ripple.
+    ///
+    /// # Arguments:
+    /// * `min_phases` - The minimum number of phases that this ripple will have.
+    /// * `rng` - The maximum number of phases that this ripple will have.
+    fn random(min_phases: usize, max_phases: usize) -> Ripple {
+        let mut rng = ThreadRng::default();
+        let phases = (0..rng.gen_range(min_phases..max_phases))
+            .map(|_| rng.gen::<f64>())
+            .collect();
+        Ripple {
+            id: Id::System(TAG.to_string()),
+            x: rng.gen::<f64>(),
+            y: rng.gen::<f64>(),
+            phases,
+            color: Color::select_random(&mut rng).to_string(),
+        }
+    }
+}
+
+/// A color which is associated with a ripple or charge.
+#[derive(Debug, Copy, Clone)]
+pub enum Color {
+    /// #80dc1a.
+    Green,
+    /// #c200fa.
+    Magenta,
+    /// #56dbb6.
+    Cyan,
+}
+
+impl Display for Color {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Color::Green => write!(f, "#80dc1a"),
+            Color::Magenta => write!(f, "#c200fa"),
+            Color::Cyan => write!(f, "#56dbb6"),
+        }
+    }
+}
+
+impl Color {
+    /// Returns a random color generated using `rng`.
+    fn select_random(rng: &mut ThreadRng) -> Color {
+        [Color::Green, Color::Magenta, Color::Cyan]
+            .choose(rng)
+            .copied()
+            .expect("Slice was not empty")
+    }
+}
+
+/// Returns an event handler which will generate a random ripple and set the state of the "ripples"
+/// lane to it.
+fn generate_ripple(context: HandlerContext<MirrorAgent>) -> impl EventHandler<MirrorAgent> {
+    context
+        .get_value(MirrorAgent::MODE)
+        .and_then(move |mode: Config| {
+            let ripple = Ripple::random(mode.min_phases, mode.max_phases);
+            context.set_value(MirrorAgent::RIPPLES, ripple)
+        })
+}
+
+/// Returns an event handler which will clean up any old charges which were not removed by the UI
+/// automatically. This may happen if the user's browser suddenly closes and a `ChargeAction::Remove`
+/// was not sent.
+fn cleanup_charges(context: HandlerContext<MirrorAgent>) -> impl EventHandler<MirrorAgent> {
+    context
+        .get_map(MirrorAgent::CHARGES)
+        .and_then(move |charges: HashMap<Id, Charge>| {
+            let now = Utc::now().time();
+            let to_remove = charges
+                .values()
+                .filter_map(move |charge| {
+                    let diff = now - charge.updated.as_ref().time();
+                    if diff.num_minutes() < 1 {
+                        None
+                    } else {
+                        trace!("Removing charge: {:?}", charge.id);
+                        Some(context.remove(MirrorAgent::CHARGES, charge.id.clone()))
+                    }
+                })
+                .collect::<Vec<_>>();
+            Sequentially::new(to_remove)
+        })
+}
diff --git a/example_apps/ripple/src/main.rs b/example_apps/ripple/src/main.rs
new file mode 100644
index 000000000..2c44580e7
--- /dev/null
+++ b/example_apps/ripple/src/main.rs
@@ -0,0 +1,125 @@
+// Copyright 2015-2024 Swim Inc.
+//
+// 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.
+
+//! Rust port of the [Ripple](https://github.com/swimos/ripple) application.
+//!
+//! Run the application using the following:
+//! ```text
+//! $ cargo run --bin ripple
+//! ```
+//!
+//! The web UI can then be found at:
+//!
+//! ```
+//! http://127.0.0.1:9002/index.html
+//! ```
+
+use std::convert::Infallible;
+use std::{error::Error, net::SocketAddr, time::Duration};
+
+use axum::body::Body;
+use axum::http::{header, HeaderValue};
+use axum::response::IntoResponse;
+use axum::routing::get;
+use axum::Router;
+use tokio::net::TcpListener;
+use tracing_subscriber::filter::LevelFilter;
+
+use swimos::agent::agent_model::AgentModel;
+use swimos::server::{Server, ServerBuilder};
+use swimos_utilities::routing::RoutePattern;
+
+use crate::agent::{MirrorAgent, MirrorLifecycle};
+
+mod agent;
+
+static HTML: HeaderValue = HeaderValue::from_static("text/html; charset=utf-8");
+const INDEX: &[u8] = include_bytes!("../ui/index.html");
+const RIPPLE: &[u8] = include_bytes!("../ui/swim-ripple.js");
+const RIPPLE_MAP: &[u8] = include_bytes!("../ui/swim-ripple.js.map");
+
+#[tokio::main]
+async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
+    example_logging()?;
+
+    let server_task = tokio::spawn(run_swim_server());
+    let ui_task = tokio::spawn(ui_server());
+    ui_task.await??;
+    server_task.await??;
+
+    Ok(())
+}
+
+/// Spawns the UI server on 0.0.0.0:9002.
+async fn ui_server() -> Result<(), Box<dyn Error + Send + Sync>> {
+    let app = ui_server_router();
+    let bind_to: SocketAddr = "0.0.0.0:9002".parse()?;
+    let listener = TcpListener::bind(bind_to).await?;
+
+    axum::serve(listener, app.into_make_service()).await?;
+
+    Ok(())
+}
+
+/// Spawns the Swim server on 0.0.0.0:9001.
+async fn run_swim_server() -> Result<(), Box<dyn Error + Send + Sync>> {
+    let mirror_agent = AgentModel::new(MirrorAgent::default, MirrorLifecycle.into_lifecycle());
+    let swim_server = ServerBuilder::with_plane_name("Ripple Plane")
+        .set_bind_addr("0.0.0.0:9001".parse().unwrap())
+        .add_route(RoutePattern::parse_str("mirror/:id")?, mirror_agent)
+        .update_config(|config| {
+            config.agent_runtime.inactive_timeout = Duration::from_secs(5 * 60);
+        })
+        .build()
+        .await?;
+
+    let (task, _handle) = swim_server.run();
+    task.await?;
+
+    println!("Server stopped");
+
+    Ok(())
+}
+
+/// Enables logging if `--enable-logging` was provided.
+fn example_logging() -> Result<(), Box<dyn Error + Send + Sync>> {
+    let args = std::env::args().collect::<Vec<_>>();
+    if args.get(1).map(String::as_str) == Some("--enable-logging") {
+        let filter = example_util::example_filter()?
+            .add_directive("ripple=trace".parse()?)
+            .add_directive(LevelFilter::WARN.into());
+        tracing_subscriber::fmt().with_env_filter(filter).init();
+    }
+    Ok(())
+}
+
+/// Builds the Axum router for serving the UI.
+pub fn ui_server_router() -> Router {
+    Router::new()
+        .route("/index.html", get(|| response(INDEX)))
+        .route("/dist/main/swim-ripple.js", get(|| response(RIPPLE)))
+        .route(
+            "/dist/main/swim-ripple.js.map",
+            get(|| response(RIPPLE_MAP)),
+        )
+}
+
+/// Axum method router handler which returns `bytes` as the response body.
+async fn response(bytes: &'static [u8]) -> impl IntoResponse {
+    let headers = [
+        (header::CONTENT_TYPE, HTML.clone()),
+        (header::CONTENT_LENGTH, HeaderValue::from(bytes.len())),
+    ];
+    Ok::<_, Infallible>((headers, Body::from(bytes)))
+}
diff --git a/example_apps/ripple/ui/index.html b/example_apps/ripple/ui/index.html
new file mode 100644
index 000000000..0c284fcce
--- /dev/null
+++ b/example_apps/ripple/ui/index.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html style="height: 100%; overflow: hidden;">
+  <head>
+    <title>Ripple</title>
+    <meta name="viewport" content="width=device-width, height=device-height, initial-scale=1, maximum-scale=1, user-scalable=0, shrink-to-fit=no, viewport-fit=cover"/>
+  </head>
+  <body style="width: 100%; height: 100%; margin: 0; overflow: auto; background-color: #040e19">
+    <div id="app" style="width: 100%; height: 100%;">
+    </div>
+    <script src="https://cdn.swimos.org/js/3.10.2/swim-system.js"></script>
+    <script src="dist/main/swim-ripple.js"></script>
+    <script>
+
+var app = new swim.HtmlAppView(document.getElementById("app"));
+
+var canvas = app.append("canvas");
+
+var mirror = new swim.MirrorView();
+mirror.captive = true;
+
+var documentUri = swim.Uri.parse(document.location.href);
+var host = documentUri.query().get("host");
+var room = documentUri.fragmentIdentifier() || "wall";
+
+var hostUri = host || "warp://localhost:9001";
+var nodeUri = swim.UriPath.from("mirror", room).toString();
+var nodeRef = swim.client.nodeRef(hostUri, nodeUri);
+var mirrorController = new swim.SwimMirrorViewController(nodeRef);
+mirror.setViewController(mirrorController);
+mirror.captive = true;
+
+canvas.append(mirror);
+
+    </script>
+  </body>
+</html>
diff --git a/example_apps/ripple/ui/swim-ripple.js b/example_apps/ripple/ui/swim-ripple.js
new file mode 100644
index 000000000..bbc41f93e
--- /dev/null
+++ b/example_apps/ripple/ui/swim-ripple.js
@@ -0,0 +1,884 @@
+(function (global, factory) {
+    typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@swim/util'), require('@swim/color'), require('@swim/structure'), require('@swim/interpolate'), require('@swim/view'), require('@swim/transition'), require('@swim/recon')) :
+        typeof define === 'function' && define.amd ? define(['exports', '@swim/util', '@swim/color', '@swim/structure', '@swim/interpolate', '@swim/view', '@swim/transition', '@swim/recon'], factory) :
+            (global = global || self, factory(global.swim = global.swim || {}, global.swim, global.swim, global.swim, global.swim, global.swim, global.swim, global.swim));
+}(this, function (exports, util, color, structure, interpolate, view, transition, recon) {
+    'use strict';
+
+    /*! *****************************************************************************
+    Copyright (c) Microsoft Corporation.
+
+    Permission to use, copy, modify, and/or distribute this software for any
+    purpose with or without fee is hereby granted.
+
+    THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+    REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+    AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+    INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+    LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+    OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+    PERFORMANCE OF THIS SOFTWARE.
+    ***************************************************************************** */
+    /* global Reflect, Promise */
+
+    var extendStatics = function (d, b) {
+        extendStatics = Object.setPrototypeOf ||
+            ({__proto__: []} instanceof Array && function (d, b) {
+                d.__proto__ = b;
+            }) ||
+            function (d, b) {
+                for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
+            };
+        return extendStatics(d, b);
+    };
+
+    function __extends(d, b) {
+        extendStatics(d, b);
+
+        function __() {
+            this.constructor = d;
+        }
+
+        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+    }
+
+    function __decorate(decorators, target, key, desc) {
+        var c = arguments.length,
+            r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+        if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+        else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+        return c > 3 && r && Object.defineProperty(target, key, r), r;
+    }
+
+    var MirrorMode = {
+        default: {
+            minRipples: 2,
+            maxRipples: 5,
+            rippleDuration: 5000,
+            rippleSpread: 300,
+        },
+    };
+    var MirrorView = (function (_super) {
+        __extends(MirrorView, _super);
+
+        function MirrorView(id, mode) {
+            if (mode === void 0) {
+                mode = MirrorMode.default;
+            }
+            var _this = _super.call(this) || this;
+            if (id === void 0) {
+                id = structure.Text.from(structure.Data.random(6).toBase64());
+            }
+            _this.onMouseDown = _this.onMouseDown.bind(_this);
+            _this.onMouseMove = _this.onMouseMove.bind(_this);
+            _this.onMouseUp = _this.onMouseUp.bind(_this);
+            _this.onTouchStart = _this.onTouchStart.bind(_this);
+            _this.onTouchMove = _this.onTouchMove.bind(_this);
+            _this.onTouchCancel = _this.onTouchCancel.bind(_this);
+            _this.onTouchEnd = _this.onTouchEnd.bind(_this);
+            _this.id = id;
+            _this.mode = mode;
+            _this.color = color.Color.fromAny(MirrorView.colors[Math.floor(MirrorView.colors.length * Math.random())]);
+            _this.captive = false;
+            _this._touchCount = 0;
+            _this._presses = {};
+            return _this;
+        }
+
+        Object.defineProperty(MirrorView.prototype, "viewController", {
+            get: function () {
+                return this._viewController;
+            },
+            enumerable: false,
+            configurable: true
+        });
+        Object.defineProperty(MirrorView.prototype, "canvasView", {
+            get: function () {
+                var parentView = this.parentView;
+                while (parentView) {
+                    if (parentView instanceof view.CanvasView) {
+                        return parentView;
+                    } else {
+                        parentView = parentView.parentView;
+                    }
+                }
+                throw new Error("not mounted");
+            },
+            enumerable: false,
+            configurable: true
+        });
+        MirrorView.prototype.onMount = function () {
+            var canvasView = this.canvasView;
+            canvasView.cursor("default");
+            if (typeof TouchEvent !== "undefined") {
+                canvasView.on("touchstart", this.onTouchStart);
+            }
+            canvasView.on("mousedown", this.onMouseDown);
+        };
+        MirrorView.prototype.onUnmount = function () {
+            var canvasView = this.canvasView;
+            if (typeof TouchEvent !== "undefined") {
+                canvasView.off("touchstart", this.onTouchStart);
+                canvasView.off("touchmove", this.onTouchMove);
+                canvasView.off("touchcancel", this.onTouchCancel);
+                canvasView.off("touchend", this.onTouchEnd);
+            }
+            canvasView.off("mousedown", this.onMouseDown);
+            canvasView.off("mousemove", this.onMouseMove);
+            canvasView.off("mouseup", this.onMouseUp);
+        };
+        MirrorView.prototype.onAnimate = function (viewContext) {
+        };
+        MirrorView.prototype.onRender = function (viewContext) {
+            var context = viewContext.renderingContext;
+            context.save();
+            var bounds = this._bounds;
+            this.renderBonds(context, bounds);
+            context.restore();
+        };
+        MirrorView.prototype.renderBonds = function (context, bounds) {
+            var childViews = this.childViews;
+            var chargeViews = [];
+            var chargeCount = 0;
+            for (var i = 0, n = childViews.length; i < n; i += 1) {
+                var childView = childViews[i];
+                if (childView instanceof MirrorView.ChargeView && childView.isPressed()) {
+                    chargeViews.push(childView);
+                    chargeCount += 1;
+                }
+            }
+            if (chargeCount >= 2) {
+                chargeViews.sort(function (a, b) {
+                    return util.Objects.compare(a.t0, b.t0);
+                });
+                var now = Date.now();
+                var a = chargeViews[0];
+                for (var i = 1; i < chargeCount; i += 1) {
+                    var b = chargeViews[i];
+                    this.renderBond(context, bounds, a, b, now);
+                }
+            }
+        };
+        MirrorView.prototype.renderBond = function (context, bounds, a, b, now) {
+            var chargeOpacity = this.mode.chargeOpacity !== void 0 ? this.mode.chargeOpacity : 1;
+            var chargeDarken = this.mode.chargeDarken !== void 0 ? this.mode.chargeDarken : 0;
+            var ax = a.centerX.value * bounds.width;
+            var ay = a.centerY.value * bounds.height;
+            var aColor = a.chargeColor.value.darker(chargeDarken).alpha(chargeOpacity);
+            var bx = b.centerX.value * bounds.width;
+            var by = b.centerY.value * bounds.height;
+            var bColor = b.chargeColor.value.darker(chargeDarken).alpha(chargeOpacity);
+            var dt = now - a.t0;
+            var pulseWidth = 2000;
+            var halfPulse = pulseWidth / 2;
+            var pulsePhase = (dt % pulseWidth) / halfPulse;
+            if (pulsePhase > 1) {
+                pulsePhase = 2 - pulsePhase;
+            }
+            context.lineWidth = 4;
+            var gradient = context.createLinearGradient(ax, ay, bx, by);
+            gradient.addColorStop(0, aColor.alpha(0).toString());
+            if (pulsePhase !== 0 && pulsePhase !== 1) {
+                var abColor = interpolate.Interpolator.color(aColor, bColor).interpolate(pulsePhase);
+                gradient.addColorStop(pulsePhase, abColor.toString());
+            }
+            gradient.addColorStop(1, bColor.alpha(0).toString());
+            context.strokeStyle = gradient;
+            context.beginPath();
+            context.moveTo(ax, ay);
+            context.lineTo(bx, by);
+            context.stroke();
+        };
+        MirrorView.prototype.didRender = function () {
+            var childViews = this.childViews;
+            var i = 0;
+            while (i < childViews.length) {
+                var childView = childViews[i];
+                if (childView instanceof MirrorView.ChargeView && !childView.isActive() && !childView.isPressed()) {
+                    this.removeChildView(childView);
+                } else {
+                    i += 1;
+                }
+            }
+        };
+        MirrorView.prototype.createCharge = function (id, t0, clientX, clientY, phases, chargeMode) {
+            var canvasView = this.canvasView;
+            var clientRect = canvasView.node.getBoundingClientRect();
+            var originX = (clientX - clientRect.left) / (clientRect.width || 1);
+            var originY = (clientY - clientRect.top) / (clientRect.height || 1);
+            var mode = this.mode;
+            var rippleCount = mode.minRipples + Math.round((mode.maxRipples - mode.minRipples) * Math.random());
+            if (phases === void 0) {
+                phases = new Array(rippleCount);
+                phases[0] = 0;
+                for (var i = 1; i < rippleCount; i += 1) {
+                    phases[i] = Math.random();
+                }
+            }
+            if (chargeMode === void 0) {
+                chargeMode = MirrorView.ChargeMode.from();
+                chargeMode.chargeColor = this.color;
+                chargeMode.rippleColor = this.color;
+            }
+            if (mode.chargeOpacity !== void 0) {
+                chargeMode.chargeOpacity = mode.chargeOpacity;
+            }
+            if (mode.chargeDarken !== void 0) {
+                chargeMode.chargeDarken = mode.chargeDarken;
+            }
+            if (mode.chargeRadius !== void 0) {
+                chargeMode.chargeRadius = mode.chargeRadius;
+            }
+            if (mode.chargeJitterRadius !== void 0) {
+                chargeMode.chargeJitterRadius = mode.chargeJitterRadius;
+            }
+            if (mode.rippleOpacity !== void 0) {
+                chargeMode.rippleOpacity = mode.rippleOpacity;
+            }
+            if (mode.rippleDarken !== void 0) {
+                chargeMode.rippleDarken = mode.rippleDarken;
+            }
+            return new MirrorView.ChargeView(id, t0, originX, originY, phases, chargeMode);
+        };
+        MirrorView.prototype.onPressDown = function (charge) {
+            this.didObserve(function (viewObserver) {
+                if (viewObserver.mirrorDidPressDown) {
+                    viewObserver.mirrorDidPressDown(charge, this);
+                }
+            });
+        };
+        MirrorView.prototype.onPressHold = function (charge) {
+            this.didObserve(function (viewObserver) {
+                if (viewObserver.mirrorDidPressHold) {
+                    viewObserver.mirrorDidPressHold(charge, this);
+                }
+            });
+        };
+        MirrorView.prototype.onPressMove = function (charge, clientX, clientY) {
+            var canvasView = this.canvasView;
+            var clientRect = canvasView.node.getBoundingClientRect();
+            var centerX = (clientX - clientRect.left) / (clientRect.width || 1);
+            var centerY = (clientY - clientRect.top) / (clientRect.height || 1);
+            charge.onPressMove(centerX, centerY);
+            if (charge.isPressed()) {
+                this.didObserve(function (viewObserver) {
+                    if (viewObserver.mirrorDidPressMove) {
+                        viewObserver.mirrorDidPressMove(charge, this);
+                    }
+                });
+            }
+        };
+        MirrorView.prototype.onPressUp = function (charge) {
+            var pressed = charge.isPressed();
+            charge.onPressUp();
+            if (pressed) {
+                this.didObserve(function (viewObserver) {
+                    if (viewObserver.mirrorDidPressUp) {
+                        viewObserver.mirrorDidPressUp(charge, this);
+                    }
+                });
+            }
+        };
+        MirrorView.prototype.onMouseDown = function (event) {
+            if (event.button !== 0 || this._presses["mouse"]) {
+                return;
+            }
+            document.body.addEventListener("mousemove", this.onMouseMove);
+            document.body.addEventListener("mouseup", this.onMouseUp);
+            var charge = this.createCharge(structure.Record.of(structure.Attr.of("User"), this.id, "mouse"), Date.now(), event.clientX, event.clientY);
+            this._presses["mouse"] = charge;
+            this.appendChildView(charge);
+            this.onPressDown(charge);
+        };
+        MirrorView.prototype.onMouseMove = function (event) {
+            var charge = this._presses["mouse"];
+            if (charge) {
+                this.onPressMove(charge, event.clientX, event.clientY);
+            }
+        };
+        MirrorView.prototype.onMouseUp = function (event) {
+            var charge = this._presses["mouse"];
+            if (charge) {
+                delete this._presses["mouse"];
+                this.onPressUp(charge);
+            }
+            document.body.removeEventListener("mousemove", this.onMouseMove);
+            document.body.removeEventListener("mouseup", this.onMouseUp);
+        };
+        MirrorView.prototype.onTouchStart = function (event) {
+            if (this.captive) {
+                event.preventDefault();
+            }
+            var touches = event.changedTouches;
+            for (var i = 0, n = touches.length; i < n; i += 1) {
+                var touch = touches[i];
+                var pressId = "touch" + touch.identifier;
+                var charge = this._presses[pressId];
+                if (charge === void 0) {
+                    if (this._touchCount === 0) {
+                        var canvasView = this.canvasView;
+                        canvasView.on("touchmove", this.onTouchMove);
+                        canvasView.on("touchcancel", this.onTouchCancel);
+                        canvasView.on("touchend", this.onTouchEnd);
+                    }
+                    charge = this.createCharge(structure.Record.of(structure.Attr.of("User"), this.id, pressId), Date.now(), touch.clientX, touch.clientY);
+                    this._presses[pressId] = charge;
+                    this._touchCount += 1;
+                    this.appendChildView(charge);
+                    this.onPressDown(charge);
+                }
+            }
+        };
+        MirrorView.prototype.onTouchMove = function (event) {
+            var touches = event.changedTouches;
+            for (var i = 0, n = touches.length; i < n; i += 1) {
+                var touch = touches[i];
+                var pressId = "touch" + touch.identifier;
+                var charge = this._presses[pressId];
+                if (charge) {
+                    this.onPressMove(charge, touch.clientX, touch.clientY);
+                }
+            }
+        };
+        MirrorView.prototype.onTouchCancel = function (event) {
+            var touches = event.changedTouches;
+            for (var i = 0, n = touches.length; i < n; i += 1) {
+                var touch = touches[i];
+                var pressId = "touch" + touch.identifier;
+                var charge = this._presses[pressId];
+                if (charge) {
+                    delete this._presses[pressId];
+                    this._touchCount -= 1;
+                    this.onPressUp(charge);
+                }
+            }
+            if (this._touchCount === 0) {
+                var canvasView = this.canvasView;
+                canvasView.off("touchmove", this.onTouchMove);
+                canvasView.off("touchcancel", this.onTouchCancel);
+                canvasView.off("touchend", this.onTouchEnd);
+            }
+        };
+        MirrorView.prototype.onTouchEnd = function (event) {
+            var touches = event.changedTouches;
+            for (var i = 0, n = touches.length; i < n; i += 1) {
+                var touch = touches[i];
+                var pressId = "touch" + touch.identifier;
+                var charge = this._presses[pressId];
+                if (charge) {
+                    delete this._presses[pressId];
+                    this._touchCount -= 1;
+                    this.onPressUp(charge);
+                }
+            }
+            if (this._touchCount === 0) {
+                var canvasView = this.canvasView;
+                canvasView.off("touchmove", this.onTouchMove);
+                canvasView.off("touchcancel", this.onTouchCancel);
+                canvasView.off("touchend", this.onTouchEnd);
+            }
+        };
+        MirrorView.colors = ["#80dc1a", "#56dbb6", "#c200fa"];
+        return MirrorView;
+    }(view.GraphicView));
+
+    var MirrorViewController = (function (_super) {
+        __extends(MirrorViewController, _super);
+
+        function MirrorViewController() {
+            return _super !== null && _super.apply(this, arguments) || this;
+        }
+
+        MirrorViewController.prototype.mirrorDidPressDown = function (charge, view) {
+        };
+        MirrorViewController.prototype.mirrorDidPressHold = function (charge, view) {
+        };
+        MirrorViewController.prototype.mirrorDidPressMove = function (charge, view) {
+        };
+        MirrorViewController.prototype.mirrorDidPressUp = function (charge, view) {
+        };
+        return MirrorViewController;
+    }(view.GraphicViewController));
+
+    var ChargeMode = {
+        from: function (chargeColor, chargeOpacity, chargeDarken, chargeRadius, chargeJitterRadius, rippleColor, rippleOpacity, rippleDarken, rippleDuration, rippleSpread, pressDelay) {
+            if (chargeColor === void 0) {
+                chargeColor = color.Color.rgb("#80dc1a");
+            }
+            if (chargeOpacity === void 0) {
+                chargeOpacity = 1;
+            }
+            if (chargeDarken === void 0) {
+                chargeDarken = 0;
+            }
+            if (chargeRadius === void 0) {
+                chargeRadius = 40;
+            }
+            if (chargeJitterRadius === void 0) {
+                chargeJitterRadius = 4;
+            }
+            if (rippleColor === void 0) {
+                rippleColor = color.Color.rgb("#80dc1a");
+            }
+            if (rippleOpacity === void 0) {
+                rippleOpacity = 1;
+            }
+            if (rippleDarken === void 0) {
+                rippleDarken = 0;
+            }
+            if (rippleDuration === void 0) {
+                rippleDuration = 5000;
+            }
+            if (rippleSpread === void 0) {
+                rippleSpread = 300;
+            }
+            if (pressDelay === void 0) {
+                pressDelay = 500;
+            }
+            return {
+                chargeColor: chargeColor,
+                chargeOpacity: chargeOpacity,
+                chargeDarken: chargeDarken,
+                chargeRadius: chargeRadius,
+                chargeJitterRadius: chargeJitterRadius,
+                rippleColor: rippleColor,
+                rippleOpacity: rippleOpacity,
+                rippleDarken: rippleDarken,
+                rippleDuration: rippleDuration,
+                rippleSpread: rippleSpread,
+                pressDelay: pressDelay,
+            };
+        }
+    };
+    MirrorView.ChargeMode = ChargeMode;
+    var ChargeView = (function (_super) {
+        __extends(ChargeView, _super);
+
+        function ChargeView(id, t0, originX, originY, phases, mode) {
+            if (mode === void 0) {
+                mode = ChargeMode.from();
+            }
+            var _this = _super.call(this) || this;
+            _this.onPressDown = _this.onPressDown.bind(_this);
+            _this.onChargeJitter = _this.onChargeJitter.bind(_this);
+            _this.id = id;
+            _this.t0 = t0;
+            _this.originX.setState(originX);
+            _this.originY.setState(originY);
+            _this.centerX.setState(originX);
+            _this.centerY.setState(originY);
+            _this.chargeColor.setState(color.Color.fromAny(mode.chargeColor));
+            _this.chargeOpacity.setState(mode.chargeOpacity);
+            _this.chargeDarken.setState(mode.chargeDarken);
+            _this.chargeRadius.setState(mode.chargeRadius);
+            _this.chargeJitter.setState(0);
+            _this.chargeJitterRadius.setState(mode.chargeJitterRadius);
+            _this.rippleColor.setState(color.Color.fromAny(mode.rippleColor));
+            _this.rippleOpacity.setState(mode.rippleOpacity);
+            _this.rippleDarken.setState(mode.rippleDarken);
+            var rippleCount = phases.length;
+            var ripples = new Array(rippleCount);
+            var _loop_1 = function (i) {
+                var phase = phases[i];
+                var ripple = new view.NumberMemberAnimator(this_1, 0);
+                var ease = function (t) {
+                    return Math.min(Math.max(0, 2 * t - (mode.rippleSpread / mode.rippleDuration) * phase), 1);
+                };
+                ripple.setState(1, transition.Transition.from(2 * mode.rippleDuration, ease));
+                ripples[i] = ripple;
+            };
+            var this_1 = this;
+            for (var i = 0; i < rippleCount; i += 1) {
+                _loop_1(i);
+            }
+            _this.ripples = ripples;
+            _this._phases = phases;
+            if (mode.pressDelay > 0) {
+                _this._pressed = false;
+                _this._pressTimer = setTimeout(_this.onPressDown, mode.pressDelay);
+            } else if (mode.pressDelay === 0) {
+                _this._pressed = true;
+                _this._pressTimer = 0;
+                _this.onChargeJitter();
+            } else {
+                _this._pressed = false;
+                _this._pressTimer = 0;
+            }
+            return _this;
+        }
+
+        Object.defineProperty(ChargeView.prototype, "viewController", {
+            get: function () {
+                return this._viewController;
+            },
+            enumerable: false,
+            configurable: true
+        });
+        ChargeView.prototype.isActive = function () {
+            return this.ripples.length !== 0;
+        };
+        ChargeView.prototype.isPressed = function () {
+            return this._pressed;
+        };
+        ChargeView.prototype.setPressed = function (pressed) {
+            if (this._pressed !== pressed) {
+                this._pressed = pressed;
+                if (this._pressed) {
+                    this.onChargeJitter();
+                }
+            }
+        };
+        ChargeView.prototype.onUnmount = function () {
+            if (this._pressTimer) {
+                clearTimeout(this._pressTimer);
+                this._pressTimer = 0;
+            }
+        };
+        ChargeView.prototype.onAnimate = function (viewContext) {
+            var t = viewContext.updateTime;
+            this.originX.onFrame(t);
+            this.originY.onFrame(t);
+            this.centerX.onFrame(t);
+            this.centerY.onFrame(t);
+            this.chargeColor.onFrame(t);
+            this.chargeOpacity.onFrame(t);
+            this.chargeDarken.onFrame(t);
+            this.chargeRadius.onFrame(t);
+            this.chargeJitter.onFrame(t);
+            this.chargeJitterRadius.onFrame(t);
+            this.rippleColor.onFrame(t);
+            this.rippleOpacity.onFrame(t);
+            this.rippleDarken.onFrame(t);
+            var ripples = this.ripples;
+            for (var i = 0, n = ripples.length; i < n; i += 1) {
+                ripples[i].onFrame(t);
+            }
+        };
+        ChargeView.prototype.onRender = function (viewContext) {
+            var context = viewContext.renderingContext;
+            context.save();
+            var bounds = this._bounds;
+            if (this._pressed) {
+                this.renderCharge(context, bounds);
+            }
+            this.renderRipples(context, bounds);
+            context.restore();
+        };
+        ChargeView.prototype.renderCharge = function (context, bounds) {
+            var centerX = this.centerX.value * bounds.width;
+            var centerY = this.centerY.value * bounds.height;
+            var chargeColor = this.chargeColor.value;
+            var radius = Math.max(0, this.chargeRadius.value + this.chargeJitter.value * this.chargeJitterRadius.value);
+            context.beginPath();
+            context.arc(centerX, centerY, radius, 0, 2 * Math.PI);
+            context.fillStyle = chargeColor.darker(this.chargeDarken.value).alpha(this.chargeOpacity.value).toString();
+            context.fill();
+        };
+        ChargeView.prototype.renderRipples = function (context, bounds) {
+            var originX = this.originX.value * bounds.width;
+            var originY = this.originY.value * bounds.height;
+            var rippleColor = this.rippleColor.value;
+            var rippleOpacity = this.rippleOpacity.value;
+            var rippleDarken = this.rippleDarken.value;
+            var maxRadius = Math.max(bounds.width, bounds.height) / 2;
+            var ripples = this.ripples;
+            var i = 0;
+            while (i < ripples.length) {
+                var ripple = ripples[i];
+                var phase = ripple.value;
+                if (0 < phase && phase < 1) {
+                    var radius = phase * maxRadius;
+                    context.beginPath();
+                    context.arc(originX, originY, radius, 0, 2 * Math.PI);
+                    context.strokeStyle = rippleColor.darker(rippleDarken).alpha(rippleOpacity - rippleOpacity * phase).toString();
+                    context.lineWidth = 1;
+                    context.stroke();
+                } else if (phase >= 1) {
+                    ripples.splice(i, 1);
+                    continue;
+                }
+                i += 1;
+            }
+        };
+        ChargeView.prototype.onPressDown = function () {
+            this._pressTimer = 0;
+            this._pressed = true;
+            var chargeRadius = this.chargeRadius.value;
+            this.chargeRadius.setState(0);
+            this.chargeRadius.setState(chargeRadius, transition.Transition.duration(300));
+            this.onChargeJitter();
+            var parentView = this.parentView;
+            if (parentView instanceof MirrorView) {
+                parentView.onPressHold(this);
+            }
+        };
+        ChargeView.prototype.onChargeJitter = function () {
+            var jitter = 0.5 - Math.random();
+            this.chargeJitter.setState(jitter, transition.Transition.duration(10).onEnd(this.onChargeJitter));
+        };
+        ChargeView.prototype.onPressMove = function (centerX, centerY) {
+            this.centerX.setState(centerX);
+            this.centerY.setState(centerY);
+        };
+        ChargeView.prototype.onPressUp = function () {
+            if (this._pressTimer) {
+                clearTimeout(this._pressTimer);
+                this._pressTimer = 0;
+            }
+            this._pressed = false;
+        };
+        __decorate([
+            view.MemberAnimator(Number)
+        ], ChargeView.prototype, "originX", void 0);
+        __decorate([
+            view.MemberAnimator(Number)
+        ], ChargeView.prototype, "originY", void 0);
+        __decorate([
+            view.MemberAnimator(Number)
+        ], ChargeView.prototype, "centerX", void 0);
+        __decorate([
+            view.MemberAnimator(Number)
+        ], ChargeView.prototype, "centerY", void 0);
+        __decorate([
+            view.MemberAnimator(color.Color)
+        ], ChargeView.prototype, "chargeColor", void 0);
+        __decorate([
+            view.MemberAnimator(Number)
+        ], ChargeView.prototype, "chargeOpacity", void 0);
+        __decorate([
+            view.MemberAnimator(Number)
+        ], ChargeView.prototype, "chargeDarken", void 0);
+        __decorate([
+            view.MemberAnimator(Number)
+        ], ChargeView.prototype, "chargeRadius", void 0);
+        __decorate([
+            view.MemberAnimator(Number)
+        ], ChargeView.prototype, "chargeJitter", void 0);
+        __decorate([
+            view.MemberAnimator(Number)
+        ], ChargeView.prototype, "chargeJitterRadius", void 0);
+        __decorate([
+            view.MemberAnimator(color.Color)
+        ], ChargeView.prototype, "rippleColor", void 0);
+        __decorate([
+            view.MemberAnimator(Number)
+        ], ChargeView.prototype, "rippleOpacity", void 0);
+        __decorate([
+            view.MemberAnimator(Number)
+        ], ChargeView.prototype, "rippleDarken", void 0);
+        return ChargeView;
+    }(view.GraphicView));
+    MirrorView.ChargeView = ChargeView;
+
+    var SwimMirrorViewController = (function (_super) {
+        __extends(SwimMirrorViewController, _super);
+
+        function SwimMirrorViewController(nodeRef) {
+            var _this = _super.call(this) || this;
+            _this.nodeRef = nodeRef;
+            _this.ripplesDownlink = null;
+            _this.chargesDownlink = null;
+            return _this;
+        }
+
+        SwimMirrorViewController.prototype.viewDidMount = function (view) {
+            this.ripplesDownlink = this.nodeRef.downlink()
+                .laneUri("ripples")
+                .observe(this)
+                .open();
+            this.chargesDownlink = this.nodeRef.downlinkMap()
+                .laneUri("charges")
+                .observe(this)
+                .open();
+        };
+        SwimMirrorViewController.prototype.viewDidUnmount = function (view) {
+            if (this.ripplesDownlink) {
+                this.ripplesDownlink.close();
+                this.ripplesDownlink = null;
+            }
+            if (this.chargesDownlink) {
+                this.chargesDownlink.close();
+                this.chargesDownlink = null;
+            }
+        };
+        SwimMirrorViewController.prototype.mirrorDidPressDown = function (charge, view) {
+            var id = charge.id;
+            var x = Math.round(charge.originX.state * 10000) / 10000;
+            var y = Math.round(charge.originY.state * 10000) / 10000;
+            var phaseCount = charge._phases.length;
+            var phases = structure.Record.create(phaseCount);
+            for (var i = 1; i < phaseCount; i += 1) {
+                var phase = Math.round(charge._phases[i] * 100) / 100;
+                phases.item(phase);
+            }
+            var color = charge.chargeColor.state.toString();
+            var command = structure.Record.create(5)
+                .attr("Ripple")
+                .slot("id", id)
+                .slot("x", x)
+                .slot("y", y)
+                .slot("phases", phases)
+                .slot("color", color);
+            this.nodeRef.command("ripple", command);
+        };
+        SwimMirrorViewController.prototype.mirrorDidPressHold = function (charge, view) {
+            var id = charge.id;
+            var x = Math.round(charge.centerX.state * 10000) / 10000;
+            var y = Math.round(charge.centerY.state * 10000) / 10000;
+            var r = charge.chargeRadius.state / 2;
+            var color = charge.chargeColor.state.toString();
+            var command = structure.Record.create(6)
+                .attr("Create")
+                .slot("id", id)
+                .slot("x", x)
+                .slot("y", y)
+                .slot("r", r)
+                .slot("color", color);
+            this.nodeRef.command("charge", command);
+        };
+        SwimMirrorViewController.prototype.mirrorDidPressMove = function (charge, view) {
+            var id = charge.id;
+            var x = Math.round(charge.centerX.state * 10000) / 10000;
+            var y = Math.round(charge.centerY.state * 10000) / 10000;
+            var r = charge.chargeRadius.state / 2;
+            var color = charge.chargeColor.state.toString();
+            var command = structure.Record.create(6)
+                .attr("Move")
+                .slot("id", id)
+                .slot("x", x)
+                .slot("y", y)
+                .slot("r", r)
+                .slot("color", color);
+            this.nodeRef.command("charge", command);
+        };
+        SwimMirrorViewController.prototype.mirrorDidPressUp = function (charge, view) {
+            var id = charge.id;
+            var command = structure.Record.create(2)
+                .attr("Remove")
+                .slot("id", id);
+            this.nodeRef.command("charge", command);
+        };
+        SwimMirrorViewController.prototype.onRemoteRipple = function (value) {
+            var view = this.view;
+            var id = value.get("id");
+            if (document.hidden || id.getItem(0).equals(view.id)) {
+                return;
+            }
+            var originX = value.get("x").numberValue(Math.random());
+            var originY = value.get("y").numberValue(Math.random());
+            var phases = value.get("phases").toAny();
+            phases.unshift(0);
+            var color = value.get("color").stringValue("#00a6ed");
+            var mode = ChargeMode.from();
+            mode.chargeRadius = 0;
+            mode.chargeColor = color;
+            if (view.mode.chargeOpacity) {
+                mode.chargeOpacity = view.mode.chargeOpacity;
+            }
+            mode.rippleColor = color;
+            if (view.mode.rippleDarken) {
+                mode.rippleDarken = view.mode.rippleDarken;
+            }
+            if (view.mode.rippleOpacity) {
+                mode.rippleOpacity = view.mode.rippleOpacity;
+            }
+            mode.pressDelay = -1;
+            var charge = new ChargeView(id, Date.now(), originX, originY, phases, mode);
+            view.appendChildView(charge);
+        };
+        SwimMirrorViewController.prototype.onRemoteUpdateCharge = function (key, value) {
+            var view = this.view;
+            if (key.getItem(0).equals(view.id)) {
+                return;
+            }
+            var id = recon.Recon.toString(key);
+            var t0 = value.get("t0").numberValue(Date.now());
+            var centerX = value.get("x").numberValue(Math.random());
+            var centerY = value.get("y").numberValue(Math.random());
+            var chargeRadius = value.get("r").numberValue(0);
+            if (!chargeRadius) {
+                return;
+            }
+            var color = value.get("color").stringValue("#00a6ed");
+            var tween = transition.Transition.duration(300);
+            var charge = view.getChildView(id);
+            if (charge) {
+                charge.t0 = t0;
+                charge.centerX.setState(centerX);
+                charge.centerY.setState(centerY);
+                charge.chargeColor(color, tween)
+                    .chargeRadius(chargeRadius, tween)
+                    .rippleColor(color, tween);
+            } else {
+                var mode = ChargeMode.from();
+                mode.chargeRadius = 0;
+                mode.chargeColor = color;
+                if (view.mode.chargeDarken) {
+                    mode.chargeDarken = view.mode.chargeDarken;
+                }
+                if (view.mode.chargeOpacity) {
+                    mode.chargeOpacity = view.mode.chargeOpacity;
+                }
+                mode.rippleColor = color;
+                if (view.mode.rippleDarken) {
+                    mode.rippleDarken = view.mode.rippleDarken;
+                }
+                if (view.mode.rippleOpacity) {
+                    mode.rippleOpacity = view.mode.rippleOpacity;
+                }
+                mode.pressDelay = 0;
+                var charge_1 = new ChargeView(key, t0, centerX, centerY, [0], mode);
+                charge_1.chargeRadius(chargeRadius, tween);
+                view.setChildView(id, charge_1);
+            }
+        };
+        SwimMirrorViewController.prototype.onRemoteRemoveCharge = function (key) {
+            var view = this.view;
+            if (key.getItem(0).equals(view.id)) {
+                return;
+            }
+            var id = recon.Recon.toString(key);
+            view.setChildView(id, null);
+        };
+        SwimMirrorViewController.prototype.removeAllCharges = function () {
+            if (this.chargesDownlink) {
+                this.chargesDownlink.forEach(function (key) {
+                    this.onRemoteRemoveCharge(key);
+                }, this);
+            }
+        };
+        SwimMirrorViewController.prototype.onEvent = function (body, downlink) {
+            if (downlink === this.ripplesDownlink) {
+                this.onRemoteRipple(body);
+            }
+        };
+        SwimMirrorViewController.prototype.didUpdate = function (key, newValue, oldValue, downlink) {
+            if (downlink === this.chargesDownlink) {
+                this.onRemoteUpdateCharge(key, newValue);
+            }
+        };
+        SwimMirrorViewController.prototype.didRemove = function (key, oldValue, downlink) {
+            if (downlink === this.chargesDownlink) {
+                this.onRemoteRemoveCharge(key);
+            }
+        };
+        SwimMirrorViewController.prototype.didConnect = function (downlink) {
+            this.removeAllCharges();
+        };
+        SwimMirrorViewController.prototype.didDisconnect = function (downlink) {
+            this.removeAllCharges();
+        };
+        return SwimMirrorViewController;
+    }(MirrorViewController));
+
+    exports.ChargeMode = ChargeMode;
+    exports.ChargeView = ChargeView;
+    exports.MirrorMode = MirrorMode;
+    exports.MirrorView = MirrorView;
+    exports.MirrorViewController = MirrorViewController;
+    exports.SwimMirrorViewController = SwimMirrorViewController;
+
+    Object.defineProperty(exports, '__esModule', {value: true});
+
+}));
+//# sourceMappingURL=swim-ripple.js.map
diff --git a/example_apps/ripple/ui/swim-ripple.js.map b/example_apps/ripple/ui/swim-ripple.js.map
new file mode 100644
index 000000000..083d188a8
--- /dev/null
+++ b/example_apps/ripple/ui/swim-ripple.js.map
@@ -0,0 +1,1 @@
+{"version":3,"file":"swim-ripple.js","sources":["../../node_modules/tslib/tslib.es6.js","../../main/MirrorView.ts","../../main/MirrorViewController.ts","../../main/ChargeView.ts","../../main/SwimMirrorViewController.ts"],"sourcesContent":["/*! *****************************************************************************\r\nCopyright (c) Microsoft Corporation.\r\n\r\nPermission to use, copy, modify, and/or distribute this software for any\r\npurpose with or without fee is hereby granted.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\r\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\r\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\r\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\r\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\r\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\r\nPERFORMANCE OF THIS SOFTWARE.\r\n***************************************************************************** */\r\n/* global Reflect, Promise */\r\n\r\nvar extendStatics = function(d, b) {\r\n    extendStatics = Object.setPrototypeOf ||\r\n        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\r\n        function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };\r\n    return extendStatics(d, b);\r\n};\r\n\r\nexport function __extends(d, b) {\r\n    extendStatics(d, b);\r\n    function __() { this.constructor = d; }\r\n    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\r\n}\r\n\r\nexport var __assign = function() {\r\n    __assign = Object.assign || function __assign(t) {\r\n        for (var s, i = 1, n = arguments.length; i < n; i++) {\r\n            s = arguments[i];\r\n            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\r\n        }\r\n        return t;\r\n    }\r\n    return __assign.apply(this, arguments);\r\n}\r\n\r\nexport function __rest(s, e) {\r\n    var t = {};\r\n    for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\r\n        t[p] = s[p];\r\n    if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\r\n        for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\r\n            if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\r\n                t[p[i]] = s[p[i]];\r\n        }\r\n    return t;\r\n}\r\n\r\nexport function __decorate(decorators, target, key, desc) {\r\n    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\r\n    if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\r\n    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\r\n    return c > 3 && r && Object.defineProperty(target, key, r), r;\r\n}\r\n\r\nexport function __param(paramIndex, decorator) {\r\n    return function (target, key) { decorator(target, key, paramIndex); }\r\n}\r\n\r\nexport function __metadata(metadataKey, metadataValue) {\r\n    if (typeof Reflect === \"object\" && typeof Reflect.metadata === \"function\") return Reflect.metadata(metadataKey, metadataValue);\r\n}\r\n\r\nexport function __awaiter(thisArg, _arguments, P, generator) {\r\n    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\r\n    return new (P || (P = Promise))(function (resolve, reject) {\r\n        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\r\n        function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\r\n        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\r\n        step((generator = generator.apply(thisArg, _arguments || [])).next());\r\n    });\r\n}\r\n\r\nexport function __generator(thisArg, body) {\r\n    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;\r\n    return g = { next: verb(0), \"throw\": verb(1), \"return\": verb(2) }, typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\r\n    function verb(n) { return function (v) { return step([n, v]); }; }\r\n    function step(op) {\r\n        if (f) throw new TypeError(\"Generator is already executing.\");\r\n        while (_) try {\r\n            if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\r\n            if (y = 0, t) op = [op[0] & 2, t.value];\r\n            switch (op[0]) {\r\n                case 0: case 1: t = op; break;\r\n                case 4: _.label++; return { value: op[1], done: false };\r\n                case 5: _.label++; y = op[1]; op = [0]; continue;\r\n                case 7: op = _.ops.pop(); _.trys.pop(); continue;\r\n                default:\r\n                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\r\n                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\r\n                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\r\n                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\r\n                    if (t[2]) _.ops.pop();\r\n                    _.trys.pop(); continue;\r\n            }\r\n            op = body.call(thisArg, _);\r\n        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\r\n        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\r\n    }\r\n}\r\n\r\nexport function __createBinding(o, m, k, k2) {\r\n    if (k2 === undefined) k2 = k;\r\n    o[k2] = m[k];\r\n}\r\n\r\nexport function __exportStar(m, exports) {\r\n    for (var p in m) if (p !== \"default\" && !exports.hasOwnProperty(p)) exports[p] = m[p];\r\n}\r\n\r\nexport function __values(o) {\r\n    var s = typeof Symbol === \"function\" && Symbol.iterator, m = s && o[s], i = 0;\r\n    if (m) return m.call(o);\r\n    if (o && typeof o.length === \"number\") return {\r\n        next: function () {\r\n            if (o && i >= o.length) o = void 0;\r\n            return { value: o && o[i++], done: !o };\r\n        }\r\n    };\r\n    throw new TypeError(s ? \"Object is not iterable.\" : \"Symbol.iterator is not defined.\");\r\n}\r\n\r\nexport function __read(o, n) {\r\n    var m = typeof Symbol === \"function\" && o[Symbol.iterator];\r\n    if (!m) return o;\r\n    var i = m.call(o), r, ar = [], e;\r\n    try {\r\n        while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\r\n    }\r\n    catch (error) { e = { error: error }; }\r\n    finally {\r\n        try {\r\n            if (r && !r.done && (m = i[\"return\"])) m.call(i);\r\n        }\r\n        finally { if (e) throw e.error; }\r\n    }\r\n    return ar;\r\n}\r\n\r\nexport function __spread() {\r\n    for (var ar = [], i = 0; i < arguments.length; i++)\r\n        ar = ar.concat(__read(arguments[i]));\r\n    return ar;\r\n}\r\n\r\nexport function __spreadArrays() {\r\n    for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;\r\n    for (var r = Array(s), k = 0, i = 0; i < il; i++)\r\n        for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)\r\n            r[k] = a[j];\r\n    return r;\r\n};\r\n\r\nexport function __await(v) {\r\n    return this instanceof __await ? (this.v = v, this) : new __await(v);\r\n}\r\n\r\nexport function __asyncGenerator(thisArg, _arguments, generator) {\r\n    if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n    var g = generator.apply(thisArg, _arguments || []), i, q = [];\r\n    return i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i;\r\n    function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }\r\n    function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\r\n    function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }\r\n    function fulfill(value) { resume(\"next\", value); }\r\n    function reject(value) { resume(\"throw\", value); }\r\n    function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\r\n}\r\n\r\nexport function __asyncDelegator(o) {\r\n    var i, p;\r\n    return i = {}, verb(\"next\"), verb(\"throw\", function (e) { throw e; }), verb(\"return\"), i[Symbol.iterator] = function () { return this; }, i;\r\n    function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === \"return\" } : f ? f(v) : v; } : f; }\r\n}\r\n\r\nexport function __asyncValues(o) {\r\n    if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n    var m = o[Symbol.asyncIterator], i;\r\n    return m ? m.call(o) : (o = typeof __values === \"function\" ? __values(o) : o[Symbol.iterator](), i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i);\r\n    function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }\r\n    function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }\r\n}\r\n\r\nexport function __makeTemplateObject(cooked, raw) {\r\n    if (Object.defineProperty) { Object.defineProperty(cooked, \"raw\", { value: raw }); } else { cooked.raw = raw; }\r\n    return cooked;\r\n};\r\n\r\nexport function __importStar(mod) {\r\n    if (mod && mod.__esModule) return mod;\r\n    var result = {};\r\n    if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];\r\n    result.default = mod;\r\n    return result;\r\n}\r\n\r\nexport function __importDefault(mod) {\r\n    return (mod && mod.__esModule) ? mod : { default: mod };\r\n}\r\n\r\nexport function __classPrivateFieldGet(receiver, privateMap) {\r\n    if (!privateMap.has(receiver)) {\r\n        throw new TypeError(\"attempted to get private field on non-instance\");\r\n    }\r\n    return privateMap.get(receiver);\r\n}\r\n\r\nexport function __classPrivateFieldSet(receiver, privateMap, value) {\r\n    if (!privateMap.has(receiver)) {\r\n        throw new TypeError(\"attempted to set private field on non-instance\");\r\n    }\r\n    privateMap.set(receiver, value);\r\n    return value;\r\n}\r\n","// Copyright 2015-2022 Swim.inc\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport {Objects} from \"@swim/util\";\nimport {BoxR2} from \"@swim/math\";\nimport {Color} from \"@swim/color\";\nimport {Value, Record, Data, Text} from \"@swim/structure\";\nimport {Interpolator} from \"@swim/interpolate\";\nimport {RenderingContext} from \"@swim/render\";\nimport {GraphicView, CanvasView, RenderViewContext} from \"@swim/view\";\nimport {ChargeMode, ChargeView} from \"./ChargeView\";\nimport {MirrorViewObserver} from \"./MirrorViewObserver\";\nimport {MirrorViewController} from \"./MirrorViewController\";\n\nexport interface MirrorMode {\n  minRipples: number;\n  maxRipples: number;\n  chargeOpacity?: number,\n  chargeDarken?: number,\n  chargeRadius?: number,\n  chargeJitterRadius: number,\n  rippleDuration: number, // time it takes a ripple to expand\n  rippleSpread: number, // time interval over which ripples emit\n  rippleOpacity?: number,\n  rippleDarken?: number,\n}\n\nexport const MirrorMode = {\n  default: {\n    minRipples: 2,\n    maxRipples: 5,\n    rippleDuration: 5000,\n    rippleSpread: 300,\n  } as MirrorMode,\n};\n\nexport class MirrorView extends GraphicView {\n  /** @hidden */\n  _viewController: MirrorViewController | null;\n  /** @hidden */\n  readonly id: Value;\n  /** @hidden */\n  readonly mode: MirrorMode;\n  /** @hidden */\n  color: Color;\n  /** @hidden */\n  captive: boolean;\n  /** @hidden */\n  _touchCount: number;\n  /** @hidden */\n  _presses: {[id: string]: ChargeView | undefined};\n\n  constructor(id?: Value, mode: MirrorMode = MirrorMode.default) {\n    super();\n    if (id === void 0) {\n      id = Text.from(Data.random(6).toBase64());\n    }\n    this.onMouseDown = this.onMouseDown.bind(this);\n    this.onMouseMove = this.onMouseMove.bind(this);\n    this.onMouseUp = this.onMouseUp.bind(this);\n    this.onTouchStart = this.onTouchStart.bind(this);\n    this.onTouchMove = this.onTouchMove.bind(this);\n    this.onTouchCancel = this.onTouchCancel.bind(this);\n    this.onTouchEnd = this.onTouchEnd.bind(this);\n    this.id = id;\n    this.mode = mode;\n    this.color = Color.fromAny(MirrorView.colors[Math.floor(MirrorView.colors.length * Math.random())]);\n    this.captive = false;\n    this._touchCount = 0;\n    this._presses = {};\n  }\n\n  get viewController(): MirrorViewController | null {\n    return this._viewController;\n  }\n\n  get canvasView(): CanvasView {\n    let parentView = this.parentView;\n    while (parentView) {\n      if (parentView instanceof CanvasView) {\n        return parentView;\n      } else {\n        parentView = parentView.parentView;\n      }\n    }\n    throw new Error(\"not mounted\");\n  }\n\n  protected onMount(): void {\n    const canvasView = this.canvasView;\n    canvasView.cursor(\"default\");\n    if (typeof TouchEvent !== \"undefined\") {\n      canvasView.on(\"touchstart\", this.onTouchStart);\n    }\n    canvasView.on(\"mousedown\", this.onMouseDown);\n  }\n\n  protected onUnmount(): void {\n    const canvasView = this.canvasView;\n    if (typeof TouchEvent !== \"undefined\") {\n      canvasView.off(\"touchstart\", this.onTouchStart);\n      canvasView.off(\"touchmove\", this.onTouchMove);\n      canvasView.off(\"touchcancel\", this.onTouchCancel);\n      canvasView.off(\"touchend\", this.onTouchEnd);\n    }\n    canvasView.off(\"mousedown\", this.onMouseDown);\n    canvasView.off(\"mousemove\", this.onMouseMove);\n    canvasView.off(\"mouseup\", this.onMouseUp);\n  }\n\n  protected onAnimate(viewContext: RenderViewContext): void {\n    // stub\n  }\n\n  protected onRender(viewContext: RenderViewContext): void {\n    const context = viewContext.renderingContext;\n    context.save();\n    const bounds = this._bounds;\n    this.renderBonds(context, bounds);\n    context.restore();\n    //this.setDirty(false); // TODO\n  }\n\n  protected renderBonds(context: RenderingContext, bounds: BoxR2): void {\n    const childViews = this.childViews;\n    const chargeViews = [];\n    let chargeCount = 0;\n    for (let i = 0, n = childViews.length; i < n; i += 1) {\n      const childView = childViews[i];\n      if (childView instanceof MirrorView.ChargeView && childView.isPressed()) {\n        chargeViews.push(childView);\n        chargeCount += 1;\n      }\n    }\n\n    if (chargeCount >= 2) {\n      chargeViews.sort(function (a: ChargeView, b: ChargeView): number {\n        return Objects.compare(a.t0, b.t0);\n      });\n      const now = Date.now();\n      const a = chargeViews[0];\n      for (let i = 1; i < chargeCount; i += 1) {\n        const b = chargeViews[i];\n        this.renderBond(context, bounds, a, b, now);\n      }\n    }\n  }\n\n  protected renderBond(context: RenderingContext, bounds: BoxR2, a: ChargeView, b: ChargeView, now: number): void {\n    const chargeOpacity = this.mode.chargeOpacity !== void 0 ? this.mode.chargeOpacity : 1;\n    const chargeDarken = this.mode.chargeDarken !== void 0 ? this.mode.chargeDarken : 0;\n    const ax = a.centerX.value! * bounds.width;\n    const ay = a.centerY.value! * bounds.height;\n    const aColor = a.chargeColor.value!.darker(chargeDarken).alpha(chargeOpacity);\n    const bx = b.centerX.value! * bounds.width;\n    const by = b.centerY.value! * bounds.height;\n    const bColor = b.chargeColor.value!.darker(chargeDarken).alpha(chargeOpacity);\n\n    const dt = now - a.t0;\n    const pulseWidth = 2000;\n    const halfPulse = pulseWidth / 2;\n    let pulsePhase = (dt % pulseWidth) / halfPulse;\n    if (pulsePhase > 1) {\n      pulsePhase = 2 - pulsePhase;\n    }\n\n    context.lineWidth = 4;\n    const gradient = context.createLinearGradient(ax, ay, bx, by);\n    gradient.addColorStop(0, aColor.alpha(0).toString());\n    if (pulsePhase !== 0 && pulsePhase !== 1) {\n      const abColor = Interpolator.color(aColor, bColor).interpolate(pulsePhase);\n      gradient.addColorStop(pulsePhase, abColor.toString());\n    }\n    gradient.addColorStop(1, bColor.alpha(0).toString());\n    context.strokeStyle = gradient;\n\n    context.beginPath();\n    context.moveTo(ax, ay);\n    context.lineTo(bx, by);\n    context.stroke();\n  }\n\n  protected didRender(): void {\n    const childViews = this.childViews;\n    let i = 0;\n    while (i < childViews.length) {\n      const childView = childViews[i];\n      if (childView instanceof MirrorView.ChargeView && !childView.isActive() && !childView.isPressed()) {\n        this.removeChildView(childView);\n      } else {\n        i += 1;\n      }\n    }\n  }\n\n  protected createCharge(id: Value, t0: number, clientX: number, clientY: number, phases?: number[], chargeMode?: ChargeMode): ChargeView {\n    const canvasView = this.canvasView;\n    const clientRect = canvasView.node.getBoundingClientRect();\n    const originX = (clientX - clientRect.left) / (clientRect.width || 1);\n    const originY = (clientY - clientRect.top) / (clientRect.height || 1);\n    const mode = this.mode;\n    const rippleCount = mode.minRipples + Math.round((mode.maxRipples - mode.minRipples) * Math.random());\n    if (phases === void 0) {\n      phases = new Array<number>(rippleCount);\n      phases[0] = 0;\n      for (let i = 1; i < rippleCount; i += 1) {\n        phases[i] = Math.random();\n      }\n    }\n    if (chargeMode === void 0) {\n      chargeMode = MirrorView.ChargeMode.from();\n      chargeMode.chargeColor = this.color;\n      chargeMode.rippleColor = this.color;\n    }\n    if (mode.chargeOpacity !== void 0) {\n      chargeMode.chargeOpacity = mode.chargeOpacity;\n    }\n    if (mode.chargeDarken !== void 0) {\n      chargeMode.chargeDarken = mode.chargeDarken;\n    }\n    if (mode.chargeRadius !== void 0) {\n      chargeMode.chargeRadius = mode.chargeRadius;\n    }\n    if (mode.chargeJitterRadius !== void 0) {\n      chargeMode.chargeJitterRadius = mode.chargeJitterRadius;\n    }\n    if (mode.rippleOpacity !== void 0) {\n      chargeMode.rippleOpacity = mode.rippleOpacity;\n    }\n    if (mode.rippleDarken !== void 0) {\n      chargeMode.rippleDarken = mode.rippleDarken;\n    }\n    return new MirrorView.ChargeView(id, t0, originX, originY, phases, chargeMode);\n  }\n\n  protected onPressDown(charge: ChargeView): void {\n    this.didObserve(function (viewObserver: MirrorViewObserver): void {\n      if (viewObserver.mirrorDidPressDown) {\n        viewObserver.mirrorDidPressDown(charge, this);\n      }\n    });\n  }\n\n  onPressHold(charge: ChargeView): void {\n    this.didObserve(function (viewObserver: MirrorViewObserver): void {\n      if (viewObserver.mirrorDidPressHold) {\n        viewObserver.mirrorDidPressHold(charge, this);\n      }\n    });\n  }\n\n  protected onPressMove(charge: ChargeView, clientX: number, clientY: number): void {\n    const canvasView = this.canvasView;\n    const clientRect = canvasView.node.getBoundingClientRect();\n    const centerX = (clientX - clientRect.left) / (clientRect.width || 1);\n    const centerY = (clientY - clientRect.top) / (clientRect.height || 1);\n    charge.onPressMove(centerX, centerY);\n\n    if (charge.isPressed()) {\n      this.didObserve(function (viewObserver: MirrorViewObserver): void {\n        if (viewObserver.mirrorDidPressMove) {\n          viewObserver.mirrorDidPressMove(charge, this);\n        }\n      });\n    }\n  }\n\n  protected onPressUp(charge: ChargeView): void {\n    const pressed = charge.isPressed();\n    charge.onPressUp();\n\n    if (pressed) {\n      this.didObserve(function (viewObserver: MirrorViewObserver): void {\n        if (viewObserver.mirrorDidPressUp) {\n          viewObserver.mirrorDidPressUp(charge, this);\n        }\n      });\n    }\n  }\n\n  protected onMouseDown(event: MouseEvent): void {\n    if (event.button !== 0 || this._presses[\"mouse\"]) {\n      return;\n    }\n\n    document.body.addEventListener(\"mousemove\", this.onMouseMove);\n    document.body.addEventListener(\"mouseup\", this.onMouseUp);\n\n    const charge = this.createCharge(Record.of(this.id, \"mouse\"), Date.now(), event.clientX, event.clientY);\n    this._presses[\"mouse\"] = charge;\n    this.appendChildView(charge);\n    this.onPressDown(charge);\n  }\n\n  protected onMouseMove(event: MouseEvent): void {\n    const charge = this._presses[\"mouse\"];\n    if (charge) {\n      this.onPressMove(charge, event.clientX, event.clientY);\n    }\n  }\n\n  protected onMouseUp(event: MouseEvent): void {\n    const charge = this._presses[\"mouse\"]!;\n    if (charge) {\n      delete this._presses[\"mouse\"];\n      this.onPressUp(charge);\n    }\n\n    document.body.removeEventListener(\"mousemove\", this.onMouseMove);\n    document.body.removeEventListener(\"mouseup\", this.onMouseUp);\n  }\n\n  protected onTouchStart(event: TouchEvent): void {\n    if (this.captive) {\n      event.preventDefault();\n    }\n    const touches = event.changedTouches;\n    for (let i = 0, n = touches.length; i < n; i += 1) {\n      const touch = touches[i];\n      const pressId = \"touch\" + touch.identifier;\n      let charge = this._presses[pressId];\n      if (charge === void 0) {\n        if (this._touchCount === 0) {\n          const canvasView = this.canvasView;\n          canvasView.on(\"touchmove\", this.onTouchMove);\n          canvasView.on(\"touchcancel\", this.onTouchCancel);\n          canvasView.on(\"touchend\", this.onTouchEnd);\n        }\n        charge = this.createCharge(Record.of(this.id, pressId), Date.now(), touch.clientX, touch.clientY);\n        this._presses[pressId] = charge;\n        this._touchCount += 1;\n        this.appendChildView(charge);\n        this.onPressDown(charge);\n      }\n    }\n  }\n\n  protected onTouchMove(event: TouchEvent): void {\n    const touches = event.changedTouches;\n    for (let i = 0, n = touches.length; i < n; i += 1) {\n      const touch = touches[i];\n      const pressId = \"touch\" + touch.identifier;\n      const charge = this._presses[pressId];\n      if (charge) {\n        this.onPressMove(charge, touch.clientX, touch.clientY);\n      }\n    }\n  }\n\n  protected onTouchCancel(event: TouchEvent): void {\n    const touches = event.changedTouches;\n    for (let i = 0, n = touches.length; i < n; i += 1) {\n      const touch = touches[i];\n      const pressId = \"touch\" + touch.identifier;\n      const charge = this._presses[pressId];\n      if (charge) {\n        delete this._presses[pressId];\n        this._touchCount -= 1;\n        this.onPressUp(charge);\n      }\n    }\n    if (this._touchCount === 0) {\n      const canvasView = this.canvasView;\n      canvasView.off(\"touchmove\", this.onTouchMove);\n      canvasView.off(\"touchcancel\", this.onTouchCancel);\n      canvasView.off(\"touchend\", this.onTouchEnd);\n    }\n  }\n\n  protected onTouchEnd(event: TouchEvent): void {\n    const touches = event.changedTouches;\n    for (let i = 0, n = touches.length; i < n; i += 1) {\n      const touch = touches[i];\n      const pressId = \"touch\" + touch.identifier;\n      const charge = this._presses[pressId];\n      if (charge) {\n        delete this._presses[pressId];\n        this._touchCount -= 1;\n        this.onPressUp(charge);\n      }\n    }\n    if (this._touchCount === 0) {\n      const canvasView = this.canvasView;\n      canvasView.off(\"touchmove\", this.onTouchMove);\n      canvasView.off(\"touchcancel\", this.onTouchCancel);\n      canvasView.off(\"touchend\", this.onTouchEnd);\n    }\n  }\n\n  /** @hidden */\n  static colors: string[] = [\"#80dc1a\", \"#56dbb6\", \"#c200fa\"];\n\n  // Forward type declarations\n  /** @hidden */\n  static ChargeMode: typeof ChargeMode; // defined by ChargeMode\n  /** @hidden */\n  static ChargeView: typeof ChargeView; // defined by ChargeView\n}\n","// Copyright 2015-2022 Swim.inc\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport {GraphicViewController} from \"@swim/view\";\nimport {MirrorView} from \"./MirrorView\";\nimport {MirrorViewObserver} from \"./MirrorViewObserver\";\nimport {ChargeView} from \"./ChargeView\";\n\nexport class MirrorViewController extends GraphicViewController<MirrorView> implements MirrorViewObserver {\n  mirrorDidPressDown(charge: ChargeView, view: MirrorView): void {\n    // hook\n  }\n\n  mirrorDidPressHold(charge: ChargeView, view: MirrorView): void {\n    // hook\n  }\n\n  mirrorDidPressMove(charge: ChargeView, view: MirrorView): void {\n    // hook\n  }\n\n  mirrorDidPressUp(charge: ChargeView, view: MirrorView): void {\n    // hook\n  }\n}\n","// Copyright 2015-2022 Swim.inc\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport {Value} from \"@swim/structure\";\nimport {BoxR2} from \"@swim/math\";\nimport {AnyColor, Color} from \"@swim/color\";\nimport {Transition} from \"@swim/transition\";\nimport {RenderingContext} from \"@swim/render\";\nimport {MemberAnimator, NumberMemberAnimator, GraphicView, GraphicViewController, RenderViewContext} from \"@swim/view\";\nimport {MirrorView} from \"./MirrorView\";\n\nexport interface ChargeMode {\n  chargeColor: AnyColor,\n  chargeOpacity: number,\n  chargeDarken: number,\n  chargeRadius: number,\n  chargeJitterRadius: number,\n  rippleColor: AnyColor,\n  rippleOpacity: number,\n  rippleDarken: number,\n  rippleDuration: number, // time it takes a ripple to expand\n  rippleSpread: number, // time interval over which ripples emit\n  pressDelay: number,\n}\n\nexport const ChargeMode = {\n  from(chargeColor: AnyColor = Color.rgb(\"#80dc1a\"),\n       chargeOpacity: number = 1,\n       chargeDarken: number = 0,\n       chargeRadius: number = 40,\n       chargeJitterRadius: number = 4,\n       rippleColor: AnyColor = Color.rgb(\"#80dc1a\"),\n       rippleOpacity: number = 1,\n       rippleDarken: number = 0,\n       rippleDuration: number = 5000,\n       rippleSpread: number = 300,\n       pressDelay: number = 500): ChargeMode {\n    return {\n      chargeColor,\n      chargeOpacity,\n      chargeDarken,\n      chargeRadius,\n      chargeJitterRadius,\n      rippleColor,\n      rippleOpacity,\n      rippleDarken,\n      rippleDuration,\n      rippleSpread,\n      pressDelay,\n    }\n  }\n}\nMirrorView.ChargeMode = ChargeMode;\n\nexport class ChargeView extends GraphicView {\n  /** @hidden */\n  _viewController: GraphicViewController<ChargeView> | null;\n  readonly id: Value;\n  t0: number;\n  ripples: NumberMemberAnimator<this>[];\n  /** @hidden */\n  _phases: number[];\n  /** @hidden */\n  _pressed: boolean;\n  /** @hidden */\n  _pressTimer: number;\n\n  constructor(id: Value, t0: number, originX: number, originY: number, phases: number[],\n              mode: ChargeMode = ChargeMode.from()) {\n    super();\n    this.onPressDown = this.onPressDown.bind(this);\n    this.onChargeJitter = this.onChargeJitter.bind(this);\n\n    this.id = id;\n    this.t0 = t0;\n    this.originX.setState(originX);\n    this.originY.setState(originY);\n    this.centerX.setState(originX);\n    this.centerY.setState(originY);\n    this.chargeColor.setState(Color.fromAny(mode.chargeColor));\n    this.chargeOpacity.setState(mode.chargeOpacity);\n    this.chargeDarken.setState(mode.chargeDarken);\n    this.chargeRadius.setState(mode.chargeRadius);\n    this.chargeJitter.setState(0);\n    this.chargeJitterRadius.setState(mode.chargeJitterRadius);\n    this.rippleColor.setState(Color.fromAny(mode.rippleColor));\n    this.rippleOpacity.setState(mode.rippleOpacity);\n    this.rippleDarken.setState(mode.rippleDarken);\n\n    const rippleCount = phases.length;\n    const ripples = new Array<NumberMemberAnimator<this>>(rippleCount);\n    for (let i = 0; i < rippleCount; i += 1) {\n      const phase = phases[i];\n      const ripple = new NumberMemberAnimator(this, 0);\n      const ease = function (t: number): number {\n        return Math.min(Math.max(0, 2 * t - (mode.rippleSpread / mode.rippleDuration) * phase), 1);\n      }\n      ripple.setState(1, Transition.from(2 * mode.rippleDuration, ease));\n      ripples[i] = ripple;\n    }\n    this.ripples = ripples;\n    this._phases = phases;\n\n    if (mode.pressDelay > 0) {\n      this._pressed = false;\n      this._pressTimer = setTimeout(this.onPressDown, mode.pressDelay) as any;\n    } else if (mode.pressDelay === 0) {\n      this._pressed = true;\n      this._pressTimer = 0;\n      this.onChargeJitter();\n    } else {\n      this._pressed = false;\n      this._pressTimer = 0;\n    }\n  }\n\n  get viewController(): GraphicViewController<ChargeView> | null {\n    return this._viewController;\n  }\n\n  isActive(): boolean {\n    return this.ripples.length !== 0;\n  }\n\n  isPressed(): boolean {\n    return this._pressed;\n  }\n\n  setPressed(pressed: boolean): void {\n    if (this._pressed !== pressed) {\n      this._pressed = pressed;\n      if (this._pressed) {\n        this.onChargeJitter();\n      }\n    }\n  }\n\n  @MemberAnimator(Number)\n  originX: MemberAnimator<this, number>;\n\n  @MemberAnimator(Number)\n  originY: MemberAnimator<this, number>;\n\n  @MemberAnimator(Number)\n  centerX: MemberAnimator<this, number>;\n\n  @MemberAnimator(Number)\n  centerY: MemberAnimator<this, number>;\n\n  @MemberAnimator(Color)\n  chargeColor: MemberAnimator<this, Color, AnyColor>;\n\n  @MemberAnimator(Number)\n  chargeOpacity: MemberAnimator<this, number>;\n\n  @MemberAnimator(Number)\n  chargeDarken: MemberAnimator<this, number>;\n\n  @MemberAnimator(Number)\n  chargeRadius: MemberAnimator<this, number>;\n\n  @MemberAnimator(Number)\n  chargeJitter: MemberAnimator<this, number>;\n\n  @MemberAnimator(Number)\n  chargeJitterRadius: MemberAnimator<this, number>;\n\n  @MemberAnimator(Color)\n  rippleColor: MemberAnimator<this, Color, AnyColor>;\n\n  @MemberAnimator(Number)\n  rippleOpacity: MemberAnimator<this, number>;\n\n  @MemberAnimator(Number)\n  rippleDarken: MemberAnimator<this, number>;\n\n  protected onUnmount(): void {\n    if (this._pressTimer) {\n      clearTimeout(this._pressTimer);\n      this._pressTimer = 0;\n    }\n  }\n\n  protected onAnimate(viewContext: RenderViewContext): void {\n    const t = viewContext.updateTime;\n    this.originX.onFrame(t);\n    this.originY.onFrame(t);\n    this.centerX.onFrame(t);\n    this.centerY.onFrame(t);\n    this.chargeColor.onFrame(t);\n    this.chargeOpacity.onFrame(t);\n    this.chargeDarken.onFrame(t);\n    this.chargeRadius.onFrame(t);\n    this.chargeJitter.onFrame(t);\n    this.chargeJitterRadius.onFrame(t);\n    this.rippleColor.onFrame(t);\n    this.rippleOpacity.onFrame(t);\n    this.rippleDarken.onFrame(t);\n    const ripples = this.ripples;\n    for (let i = 0, n = ripples.length; i < n; i += 1) {\n      ripples[i].onFrame(t);\n    }\n  }\n\n  protected onRender(viewContext: RenderViewContext): void {\n    const context = viewContext.renderingContext;\n    context.save();\n    const bounds = this._bounds;\n    if (this._pressed) {\n      this.renderCharge(context, bounds);\n    }\n    this.renderRipples(context, bounds);\n    context.restore();\n    //this.setDirty(false);\n  }\n\n  protected renderCharge(context: RenderingContext, bounds: BoxR2): void {\n    const centerX = this.centerX.value! * bounds.width;\n    const centerY = this.centerY.value! * bounds.height;\n    const chargeColor = this.chargeColor.value!;\n    const radius = Math.max(0, this.chargeRadius.value! + this.chargeJitter.value! * this.chargeJitterRadius.value!);\n\n    context.beginPath();\n    context.arc(centerX, centerY, radius, 0, 2 * Math.PI);\n    context.fillStyle = chargeColor.darker(this.chargeDarken.value!).alpha(this.chargeOpacity.value!).toString();\n    context.fill();\n  }\n\n  protected renderRipples(context: RenderingContext, bounds: BoxR2): void {\n    const originX = this.originX.value! * bounds.width;\n    const originY = this.originY.value! * bounds.height;\n    const rippleColor = this.rippleColor.value!;\n    const rippleOpacity = this.rippleOpacity.value!;\n    const rippleDarken = this.rippleDarken.value!;\n    const maxRadius = Math.max(bounds.width, bounds.height) / 2;\n\n    const ripples = this.ripples;\n    let i = 0;\n    while (i < ripples.length) {\n      const ripple = ripples[i];\n      const phase = ripple.value!;\n      if (0 < phase && phase < 1) {\n        const radius = phase * maxRadius;\n        context.beginPath();\n        context.arc(originX, originY, radius, 0, 2 * Math.PI);\n        context.strokeStyle = rippleColor.darker(rippleDarken).alpha(rippleOpacity - rippleOpacity * phase).toString();\n        context.lineWidth = 1;\n        context.stroke();\n      } else if (phase >= 1) {\n        ripples.splice(i, 1);\n        continue;\n      }\n      i += 1;\n    }\n  }\n\n  protected onPressDown(): void {\n    this._pressTimer = 0;\n    this._pressed = true;\n    const chargeRadius = this.chargeRadius.value!;\n    this.chargeRadius.setState(0);\n    this.chargeRadius.setState(chargeRadius, Transition.duration<number>(300));\n    this.onChargeJitter();\n\n    const parentView = this.parentView;\n    if (parentView instanceof MirrorView) {\n      parentView.onPressHold(this);\n    }\n  }\n\n  onChargeJitter(): void {\n    const jitter = 0.5 - Math.random();\n    this.chargeJitter.setState(jitter, Transition.duration<number>(10).onEnd(this.onChargeJitter));\n  }\n\n  onPressMove(centerX: number, centerY: number): void {\n    this.centerX.setState(centerX);\n    this.centerY.setState(centerY);\n    //this.animate(); TODO\n  }\n\n  onPressUp(): void {\n    if (this._pressTimer) {\n      clearTimeout(this._pressTimer);\n      this._pressTimer = 0;\n    }\n    this._pressed = false;\n    //this.animate(); //TODO\n  }\n}\nMirrorView.ChargeView = ChargeView;\n","// Copyright 2015-2022 Swim.inc\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport {AnyValue, Value, Record} from \"@swim/structure\";\nimport {Recon} from \"@swim/recon\";\nimport {NodeRef, Downlink, EventDownlink, EventDownlinkObserver, MapDownlink, MapDownlinkObserver} from \"@swim/client\";\nimport {Transition} from \"@swim/transition\";\nimport {MirrorView} from \"./MirrorView\";\nimport {MirrorViewController} from \"./MirrorViewController\";\nimport {ChargeMode, ChargeView} from \"./ChargeView\";\n\nexport class SwimMirrorViewController extends MirrorViewController implements EventDownlinkObserver, MapDownlinkObserver<Value, Value, AnyValue, AnyValue> {\n  nodeRef: NodeRef;\n  ripplesDownlink: EventDownlink | null;\n  chargesDownlink: MapDownlink<Value, Value, AnyValue, AnyValue> | null;\n\n  constructor(nodeRef: NodeRef) {\n    super();\n    this.nodeRef = nodeRef;\n    this.ripplesDownlink = null;\n    this.chargesDownlink = null;\n  }\n\n  viewDidMount(view: MirrorView): void {\n    this.ripplesDownlink = this.nodeRef.downlink()\n        .laneUri(\"ripples\")\n        .observe(this)\n        .open();\n    this.chargesDownlink = this.nodeRef.downlinkMap()\n        .laneUri(\"charges\")\n        .observe(this)\n        .open();\n  }\n\n  viewDidUnmount(view: MirrorView): void {\n    if (this.ripplesDownlink) {\n      this.ripplesDownlink.close();\n      this.ripplesDownlink = null;\n    }\n    if (this.chargesDownlink) {\n      this.chargesDownlink.close();\n      this.chargesDownlink = null;\n    }\n  }\n\n  mirrorDidPressDown(charge: ChargeView, view: MirrorView): void {\n    const id = charge.id;\n    const x = Math.round(charge.originX.state! * 10000) / 10000;\n    const y = Math.round(charge.originY.state! * 10000) / 10000;\n    const phaseCount = charge._phases.length;\n    const phases = Record.create(phaseCount);\n    for (let i = 1; i < phaseCount; i += 1) {\n      const phase = Math.round(charge._phases[i] * 100) / 100;\n      phases.item(phase);\n    }\n    const color = charge.chargeColor.state!.toString();\n    const command = Record.create(5)\n        .slot(\"id\", id)\n        .slot(\"x\", x)\n        .slot(\"y\", y)\n        .slot(\"phases\", phases)\n        .slot(\"color\", color);\n    this.nodeRef.command(\"ripple\", command);\n  }\n\n  mirrorDidPressHold(charge: ChargeView, view: MirrorView): void {\n    const id = charge.id;\n    const x = Math.round(charge.centerX.state! * 10000) / 10000;\n    const y = Math.round(charge.centerY.state! * 10000) / 10000;\n    const r = charge.chargeRadius.state! / 2;\n    const color = charge.chargeColor.state!.toString();\n    const command = Record.create(6)\n        .attr(\"hold\")\n        .slot(\"id\", id)\n        .slot(\"x\", x)\n        .slot(\"y\", y)\n        .slot(\"r\", r)\n        .slot(\"color\", color);\n    this.nodeRef.command(\"charge\", command);\n  }\n\n  mirrorDidPressMove(charge: ChargeView, view: MirrorView): void {\n    const id = charge.id;\n    const x = Math.round(charge.centerX.state! * 10000) / 10000;\n    const y = Math.round(charge.centerY.state! * 10000) / 10000;\n    const r = charge.chargeRadius.state! / 2;\n    const color = charge.chargeColor.state!.toString();\n    const command = Record.create(6)\n        .attr(\"move\")\n        .slot(\"id\", id)\n        .slot(\"x\", x)\n        .slot(\"y\", y)\n        .slot(\"r\", r)\n        .slot(\"color\", color);\n    this.nodeRef.command(\"charge\", command);\n  }\n\n  mirrorDidPressUp(charge: ChargeView, view: MirrorView): void {\n    const id = charge.id;\n    const command = Record.create(2)\n        .attr(\"up\")\n        .slot(\"id\", id);\n    this.nodeRef.command(\"charge\", command);\n  }\n\n  protected onRemoteRipple(value: Value): void {\n    const view = this.view!;\n    const id = value.get(\"id\");\n    if (document.hidden || id.getItem(0).equals(view.id)) {\n      return;\n    }\n    const originX = value.get(\"x\").numberValue(Math.random());\n    const originY = value.get(\"y\").numberValue(Math.random());\n    const phases = value.get(\"phases\").toAny() as number[];\n    phases.unshift(0);\n    const color = value.get(\"color\").stringValue(\"#00a6ed\");\n    const mode = ChargeMode.from();\n    mode.chargeRadius = 0;\n    mode.chargeColor = color;\n    if (view.mode.chargeOpacity) {\n      mode.chargeOpacity = view.mode.chargeOpacity;\n    }\n    mode.rippleColor = color;\n    if (view.mode.rippleDarken) {\n      mode.rippleDarken = view.mode.rippleDarken;\n    }\n    if (view.mode.rippleOpacity) {\n      mode.rippleOpacity = view.mode.rippleOpacity;\n    }\n    mode.pressDelay = -1;\n    const charge = new ChargeView(id, Date.now(), originX, originY, phases, mode);\n    view.appendChildView(charge);\n  }\n\n  protected onRemoteUpdateCharge(key: Value, value: Value): void {\n    const view = this.view!;\n    if (key.getItem(0).equals(view.id)) {\n      return;\n    }\n    const id = Recon.toString(key);\n    const t0 = value.get(\"t0\").numberValue(Date.now());\n    const centerX = value.get(\"x\").numberValue(Math.random());\n    const centerY = value.get(\"y\").numberValue(Math.random());\n    const chargeRadius = value.get(\"r\").numberValue(0);\n    if (!chargeRadius) {\n      return;\n    }\n    const color = value.get(\"color\").stringValue(\"#00a6ed\");\n    const tween = Transition.duration<any>(300);\n    let charge = view.getChildView(id) as ChargeView | null;\n    if (charge) {\n      charge.t0 = t0;\n      charge.centerX.setState(centerX);\n      charge.centerY.setState(centerY);\n      charge.chargeColor(color, tween)\n            .chargeRadius(chargeRadius, tween)\n            .rippleColor(color, tween);\n    } else {\n      const mode = ChargeMode.from();\n      mode.chargeRadius = 0;\n      mode.chargeColor = color;\n      if (view.mode.chargeDarken) {\n        mode.chargeDarken = view.mode.chargeDarken;\n      }\n      if (view.mode.chargeOpacity) {\n        mode.chargeOpacity = view.mode.chargeOpacity;\n      }\n      mode.rippleColor = color;\n      if (view.mode.rippleDarken) {\n        mode.rippleDarken = view.mode.rippleDarken;\n      }\n      if (view.mode.rippleOpacity) {\n        mode.rippleOpacity = view.mode.rippleOpacity;\n      }\n      mode.pressDelay = 0;\n      const charge = new ChargeView(key, t0, centerX, centerY, [0], mode);\n      charge.chargeRadius(chargeRadius, tween);\n      view.setChildView(id, charge);\n    }\n  }\n\n  protected onRemoteRemoveCharge(key: Value): void {\n    const view = this.view!;\n    if (key.getItem(0).equals(view.id)) {\n      return;\n    }\n    const id = Recon.toString(key);\n    view.setChildView(id, null);\n  }\n\n  protected removeAllCharges(): void {\n    if (this.chargesDownlink) {\n      this.chargesDownlink.forEach(function (key: Value) {\n        this.onRemoteRemoveCharge(key);\n      }, this);\n    }\n  }\n\n  onEvent(body: Value, downlink: Downlink): void {\n    if (downlink === this.ripplesDownlink) {\n      this.onRemoteRipple(body);\n    }\n  }\n\n  didUpdate(key: Value, newValue: Value, oldValue: Value, downlink: MapDownlink<Value, Value, AnyValue, AnyValue>): void {\n    if (downlink === this.chargesDownlink) {\n      this.onRemoteUpdateCharge(key, newValue);\n    }\n  }\n\n  didRemove(key: Value, oldValue: Value, downlink: MapDownlink<Value, Value, AnyValue, AnyValue>): void {\n    if (downlink === this.chargesDownlink) {\n      this.onRemoteRemoveCharge(key);\n    }\n  }\n\n  didConnect(downlink: Downlink): void {\n    this.removeAllCharges();\n  }\n\n  didDisconnect(downlink: Downlink): void {\n    this.removeAllCharges();\n  }\n}\n"],"names":["Text","Data","Color","CanvasView","Objects","Interpolator","Record","GraphicView","GraphicViewController","NumberMemberAnimator","Transition","MemberAnimator","Recon"],"mappings":"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"}
\ No newline at end of file


Test patch: