From c2b0a3de4dca16cb9143acffdcc7a9ad1a730b50 Mon Sep 17 00:00:00 2001 From: Jakob Odersky Date: Fri, 27 Mar 2015 12:30:52 +0100 Subject: Clean up internal structure of dashboard ui. * move instruments to separate files * instruments are now updated reactively by means of scalarx Vars * upgrade scalarx version * remove panels: layout and message routing is now handled centrally --- project/Build.scala | 2 +- .../src/main/scala/vfd/dashboard/ui/Layout.scala | 169 ++++++++++++++------- .../dashboard/ui/components/SvgInstrument.scala | 57 ------- .../vfd/dashboard/ui/components/instruments.scala | 126 --------------- .../vfd/dashboard/ui/instruments/Altimeter.scala | 19 +++ .../vfd/dashboard/ui/instruments/Balance.scala | 27 ++++ .../scala/vfd/dashboard/ui/instruments/Bar.scala | 19 +++ .../vfd/dashboard/ui/instruments/Compass.scala | 18 +++ .../vfd/dashboard/ui/instruments/Generic.scala | 40 +++++ .../vfd/dashboard/ui/instruments/Horizon.scala | 20 +++ .../vfd/dashboard/ui/instruments/Instrument.scala | 28 ++++ .../scala/vfd/dashboard/ui/instruments/Led.scala | 18 +++ .../dashboard/ui/instruments/SvgInstrument.scala | 54 +++++++ .../vfd/dashboard/ui/panels/Communication.scala | 113 -------------- .../scala/vfd/dashboard/ui/panels/Primary.scala | 46 ------ 15 files changed, 358 insertions(+), 398 deletions(-) delete mode 100644 vfd-dashboard/src/main/scala/vfd/dashboard/ui/components/SvgInstrument.scala delete mode 100644 vfd-dashboard/src/main/scala/vfd/dashboard/ui/components/instruments.scala create mode 100644 vfd-dashboard/src/main/scala/vfd/dashboard/ui/instruments/Altimeter.scala create mode 100644 vfd-dashboard/src/main/scala/vfd/dashboard/ui/instruments/Balance.scala create mode 100644 vfd-dashboard/src/main/scala/vfd/dashboard/ui/instruments/Bar.scala create mode 100644 vfd-dashboard/src/main/scala/vfd/dashboard/ui/instruments/Compass.scala create mode 100644 vfd-dashboard/src/main/scala/vfd/dashboard/ui/instruments/Generic.scala create mode 100644 vfd-dashboard/src/main/scala/vfd/dashboard/ui/instruments/Horizon.scala create mode 100644 vfd-dashboard/src/main/scala/vfd/dashboard/ui/instruments/Instrument.scala create mode 100644 vfd-dashboard/src/main/scala/vfd/dashboard/ui/instruments/Led.scala create mode 100644 vfd-dashboard/src/main/scala/vfd/dashboard/ui/instruments/SvgInstrument.scala delete mode 100644 vfd-dashboard/src/main/scala/vfd/dashboard/ui/panels/Communication.scala delete mode 100644 vfd-dashboard/src/main/scala/vfd/dashboard/ui/panels/Primary.scala diff --git a/project/Build.scala b/project/Build.scala index 9dd17c5..2cd1589 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -80,7 +80,7 @@ object ApplicationBuild extends Build { val scalajs = Seq( libraryDependencies ++= Seq( "org.scala-js" %%% "scalajs-dom" % "0.8.0", - "com.lihaoyi" %%% "scalatags" % "0.4.6", + "com.lihaoyi" %%% "scalatags" % "0.5.0", "com.lihaoyi" %%% "scalarx" % "0.2.8" ) ) diff --git a/vfd-dashboard/src/main/scala/vfd/dashboard/ui/Layout.scala b/vfd-dashboard/src/main/scala/vfd/dashboard/ui/Layout.scala index 0264e5f..f17e91a 100644 --- a/vfd-dashboard/src/main/scala/vfd/dashboard/ui/Layout.scala +++ b/vfd-dashboard/src/main/scala/vfd/dashboard/ui/Layout.scala @@ -1,67 +1,25 @@ package vfd.dashboard.ui -import org.scalajs.dom.html -import scalatags.JsDom.all.ExtendedString -import scalatags.JsDom.all.Int2CssNumber -import scalatags.JsDom.all.`class` -import scalatags.JsDom.all.div -import scalatags.JsDom.all.header -import scalatags.JsDom.all.height -import scalatags.JsDom.all.iframe -import scalatags.JsDom.all.p -import scalatags.JsDom.all.src -import scalatags.JsDom.all.stringAttr -import scalatags.JsDom.all.stringFrag -import scalatags.JsDom.all.stringPixelStyle -import scalatags.JsDom.all.style -import scalatags.JsDom.all.width -import scalatags.JsDom.all.button -import scalatags.JsDom.all.id +import org.mavlink.messages.Attitude +import org.mavlink.messages.Motor +import org.mavlink.messages.Power + +import rx.Obs import scalatags.JsDom.all._ -import scalatags.jsdom._ -import scalatags.jsdom.Frag import vfd.dashboard.Environment import vfd.dashboard.MavlinkSocket -import vfd.dashboard.ui.panels.Communication -import vfd.dashboard.ui.panels.Primary -import org.scalajs.dom.MouseEvent -import org.scalajs.dom +import vfd.dashboard.ui.instruments.Altimeter +import vfd.dashboard.ui.instruments.Balance +import vfd.dashboard.ui.instruments.Bar +import vfd.dashboard.ui.instruments.Compass +import vfd.dashboard.ui.instruments.Generic +import vfd.dashboard.ui.instruments.Horizon class Layout(socket: MavlinkSocket)(implicit env: Environment) { private def panel(contents: Frag*) = div(`class` := "d-panel")(contents: _*) - def layout = - div(`class` := "d-container d-column")( - div(`class` := "d-above")( - top), - div(`class` := "d-above d-container d-row")( - panel(modes), - panel(infos)), - div(`class` := "d-container d-row")( - div(`class` := "d-container d-details")( - panel("foo")), - div(`class` := "d-container d-left")( - left), - div(`class` := "d-container d-column d-middle")( - div(`class` := "d-container d-center")( - center), - div(`class` := "d-container d-below")( - below)), - div(`class` := "d-container d-right")( - right))) - - def top = header( - div("Flight Control Panel"), - div("00:00:00"), - div("System #")) - - def left = panel(map) - def center = panel(feed) - def below = panel(Primary(socket)) - def right = panel(Communication(socket)) - - def mode(name: String, kind: String, on: Boolean = false) = div(`class` := s"mode $kind ${if (!on) "off"}")(name) + private def mode(name: String, kind: String, on: Boolean = false) = div(`class` := s"mode $kind ${if (!on) "off"}")(name) val modes = div( mode("MANUAL", "warning", true), @@ -91,5 +49,106 @@ class Layout(socket: MavlinkSocket)(implicit env: Environment) { val feed = div(style := "width: 100%; height: 460px; color: #ffffff; background-color: #c2c2c2; text-align: center;")( p(style := "padding-top: 220px")("video feed")) - def element: html.Element = layout.render + val altimeter = new Altimeter + val horizon = new Horizon + val compass = new Compass + val motor0 = new Generic(0, 50, 100, "%") + val motor1 = new Generic(0, 50, 100, "%") + val motor2 = new Generic(0, 50, 100, "%") + val motor3 = new Generic(0, 50, 100, "%") + val powerDistribution = new Balance() + val batteryLevel = new Bar() + + val top = header( + div("Flight Control Panel"), + div("00:00:00"), + div("System #")) + + val left = panel( + map, + table(`class` := "table-instrument")( + thead("Motors"), + tbody( + tr( + td(motor0.element), + td(), + td(motor1.element), + td() + ), + tr( + td(), + td(powerDistribution.element), + td(), + td() + ), + tr( + td(motor2.element), + td(), + td(motor3.element), + td() + ) + ) + ) + ) + + val center = panel(feed) + + val below = panel( + table(`class` := "table-instrument")( + tbody( + tr( + td(compass.element), + td(horizon.element), + td(altimeter.element) + ) + ) + ) + ) + + val right = panel() + + val element = div(`class` := "d-container d-column")( + div(`class` := "d-above")( + top), + div(`class` := "d-above d-container d-row")( + panel(modes), + panel(infos)), + div(`class` := "d-container d-row")( + div(`class` := "d-container d-details")( + panel("foo")), + div(`class` := "d-container d-left")( + left), + div(`class` := "d-container d-column d-middle")( + div(`class` := "d-container d-center")( + center), + div(`class` := "d-container d-below")( + below) + ), + div(`class` := "d-container d-right")( + right + ) + ) + ).render + + //message router + Obs(socket.message, skipInitial = true) { + socket.message() match { + + case Motor(m0, m1, m2, m3) => + motor0.value() = m0 + motor1.value() = m1 + motor2.value() = m2 + motor3.value() = m3 + powerDistribution.value() = (m0, m1, m2, m3) + + case Power(mV) => + batteryLevel.value() = (100 * (mV - 9600) / 12600) + + case Attitude(roll, pitch, yaw) => + horizon.value() = (pitch, roll) + compass.value() = (yaw) + + } + } + } \ No newline at end of file diff --git a/vfd-dashboard/src/main/scala/vfd/dashboard/ui/components/SvgInstrument.scala b/vfd-dashboard/src/main/scala/vfd/dashboard/ui/components/SvgInstrument.scala deleted file mode 100644 index dc8ee5e..0000000 --- a/vfd-dashboard/src/main/scala/vfd/dashboard/ui/components/SvgInstrument.scala +++ /dev/null @@ -1,57 +0,0 @@ -package vfd.dashboard.ui.components - -import scala.scalajs.js.Any.fromFunction1 - -import org.scalajs.dom -import org.scalajs.dom.html - -import scalatags.JsDom.all.ExtendedString -import scalatags.JsDom.all.Int2CssNumber -import scalatags.JsDom.all.`object` -import scalatags.JsDom.all.stringAttr -import scalatags.JsDom.all.stringFrag -import scalatags.JsDom.all.stringPixelStyle -import scalatags.JsDom.all.`type` -import scalatags.JsDom.all.width -import vfd.dashboard.Environment - -trait SvgInstrument[A] { - - /** SVG object element that contains the rendered instrument */ - def element: html.Object - - /** Actual svg document */ - protected def content = element.contentDocument - - /** Moveable parts of the instrument */ - protected def moveable: Seq[html.Element] - - /** Updates the instrument to show a new value */ - def update(value: A): Unit - - protected def load(event: dom.Event): Unit = { - for (part <- moveable) { - part.style.transition = "transform 250ms ease-out" - } - } - - element.addEventListener("load", (e: dom.Event) => load(e)) -} - -object SvgInstrument { - - def svg(name: String)(implicit app: Environment): html.Object = { - val path = app.asset("images/instruments/" + name + ".svg") - `object`(`type` := "image/svg+xml", "data".attr := path, width := 100.pct)( - "Error loading instrument " + name).render - } - - def translate(elem: html.Element, x: Int, y: Int): Unit = { - elem.style.transform = "translate(" + x + "px, " + y + "px)"; - } - - def rotate(elem: html.Element, deg: Int): Unit = { - elem.style.transform = "rotateZ(" + deg + "deg)"; - } - -} \ No newline at end of file diff --git a/vfd-dashboard/src/main/scala/vfd/dashboard/ui/components/instruments.scala b/vfd-dashboard/src/main/scala/vfd/dashboard/ui/components/instruments.scala deleted file mode 100644 index a508eb0..0000000 --- a/vfd-dashboard/src/main/scala/vfd/dashboard/ui/components/instruments.scala +++ /dev/null @@ -1,126 +0,0 @@ -package vfd.dashboard.ui.components - -import org.scalajs.dom -import org.scalajs.dom.html - -import scalatags.JsDom.all.ExtendedString -import scalatags.JsDom.all.Int2CssNumber -import scalatags.JsDom.all.`object` -import scalatags.JsDom.all.stringAttr -import scalatags.JsDom.all.stringFrag -import scalatags.JsDom.all.stringPixelStyle -import scalatags.JsDom.all.`type` -import scalatags.JsDom.all.width -import vfd.dashboard.Environment - -class Led(implicit env: Environment) extends SvgInstrument[String] { - lazy val element = `object`(`type` := "image/svg+xml", "data".attr := env.asset("images/leds/led.svg"), width := 100.pct)( - "Error loading led.").render - - def update(color: String) = { - content.getElementById("light").setAttribute("fill", color) - } - - protected def moveable = Seq() - -} - -class Horizon(implicit env: Environment) extends SvgInstrument[(Double, Double)] { - lazy val element = SvgInstrument.svg("horizon") - - def pitch = content.getElementById("pitch").asInstanceOf[html.Element] - def roll = content.getElementById("roll").asInstanceOf[html.Element] - protected def moveable = Seq(pitch, roll) - - def update(pitchRoll: (Double, Double)) = { - SvgInstrument.translate(pitch, 0, pitchRoll._1.toInt) - SvgInstrument.rotate(roll, pitchRoll._2.toInt) - } -} - -class Altimeter(implicit env: Environment) extends SvgInstrument[Double] { - lazy val element = SvgInstrument.svg("altimeter") - - def hand = content.getElementById("hand").asInstanceOf[html.Element] - protected def moveable = Seq(hand) - - // 36deg === 1m - def update(altitude: Double) = { - SvgInstrument.rotate(hand, (altitude * 36).toInt) - } -} - -class Compass(implicit env: Environment) extends SvgInstrument[Double] { - lazy val element = SvgInstrument.svg("compass") - - def plate = content.getElementById("heading").asInstanceOf[html.Element] - protected def moveable = Seq(plate) - - def update(heading: Double) = { - SvgInstrument.rotate(plate, heading.toInt) - } -} - -class Generic( - min: Double, - med: Double, - max: Double, - unit: String)(implicit env: Environment) extends SvgInstrument[Double] { - - lazy val element = SvgInstrument.svg("generic") - - def handElement = content.getElementById("hand").asInstanceOf[html.Element] - def unitElement = content.getElementById("unit") - def valueElement = content.getElementById("value") - def minElement = content.getElementById("min") - def medElement = content.getElementById("med") - def maxElement = content.getElementById("max") - protected def moveable = Seq(handElement) - - override protected def load(e: dom.Event) = { - unitElement.textContent = unit - minElement.textContent = min.toString - medElement.textContent = med.toString - maxElement.textContent = max.toString - update(min) - super.load(e) - } - - def update(value: Double) = { - SvgInstrument.rotate(handElement, (value * 270 / (max - min)).toInt) - valueElement.textContent = value.toString - } -} - -class Bar(implicit env: Environment) extends SvgInstrument[Double] { - - lazy val element = SvgInstrument.svg("bar") - - def level = content.getElementById("level").asInstanceOf[html.Element] - protected def moveable = Seq(level) - - def update(value: Double) = { - SvgInstrument.translate(level, 0, (97 * (1 - value / 100)).toInt) - } - -} - -class Balance(implicit env: Environment) extends SvgInstrument[(Double, Double, Double, Double)] { - lazy val element = SvgInstrument.svg("balance") - - def position = content.getElementById("position").asInstanceOf[html.Element] - protected def moveable = Seq(position) - - def update(value: (Double, Double, Double, Double)) = { - val m0 = value._1 - val m1 = value._2 - val m2 = value._3 - val m3 = value._4 - val s = m0 + m1 + m2 + m3 - val i = (m0 - m2) / s - val j = (m1 - m3) / s - val x = 0.5 * (i - j) - val y = 0.5 * (-i - j) - SvgInstrument.translate(position, (x * 50).toInt, (y * 50).toInt) - } -} \ No newline at end of file diff --git a/vfd-dashboard/src/main/scala/vfd/dashboard/ui/instruments/Altimeter.scala b/vfd-dashboard/src/main/scala/vfd/dashboard/ui/instruments/Altimeter.scala new file mode 100644 index 0000000..9ff3bb5 --- /dev/null +++ b/vfd-dashboard/src/main/scala/vfd/dashboard/ui/instruments/Altimeter.scala @@ -0,0 +1,19 @@ +package vfd.dashboard.ui.instruments + +import org.scalajs.dom.html +import vfd.dashboard.Environment + +class Altimeter(implicit env: Environment) extends SvgInstrument[Double] { + import SvgInstrument._ + + val initial = 0.0 + + lazy val element = svgObject("altimeter") + lazy val hand = part("hand") + lazy val moveable = Seq(hand) + + // 36deg === 1m + protected def update(altitude: Double) = { + rotate(hand, (altitude * 36).toInt) + } +} \ No newline at end of file diff --git a/vfd-dashboard/src/main/scala/vfd/dashboard/ui/instruments/Balance.scala b/vfd-dashboard/src/main/scala/vfd/dashboard/ui/instruments/Balance.scala new file mode 100644 index 0000000..ef5a3cc --- /dev/null +++ b/vfd-dashboard/src/main/scala/vfd/dashboard/ui/instruments/Balance.scala @@ -0,0 +1,27 @@ +package vfd.dashboard.ui.instruments + +import org.scalajs.dom.html +import vfd.dashboard.Environment + +class Balance(implicit env: Environment) extends SvgInstrument[(Double, Double, Double, Double)] { + import SvgInstrument._ + + val initial = (0.0, 0.0, 0.0, 0.0) + + lazy val element = svgObject("balance") + lazy val position = part("position") + lazy val moveable = Seq(position) + + protected def update(value: (Double, Double, Double, Double)) = { + val m0 = value._1 + val m1 = value._2 + val m2 = value._3 + val m3 = value._4 + val s = m0 + m1 + m2 + m3 + val i = (m0 - m2) / s + val j = (m1 - m3) / s + val x = 0.5 * (i - j) + val y = 0.5 * (-i - j) + translate(position, (x * 50).toInt, (y * 50).toInt) + } +} \ No newline at end of file diff --git a/vfd-dashboard/src/main/scala/vfd/dashboard/ui/instruments/Bar.scala b/vfd-dashboard/src/main/scala/vfd/dashboard/ui/instruments/Bar.scala new file mode 100644 index 0000000..7cda301 --- /dev/null +++ b/vfd-dashboard/src/main/scala/vfd/dashboard/ui/instruments/Bar.scala @@ -0,0 +1,19 @@ +package vfd.dashboard.ui.instruments + +import org.scalajs.dom.html +import vfd.dashboard.Environment + +class Bar(implicit env: Environment) extends SvgInstrument[Double] { + import SvgInstrument._ + + val initial = 0.0 + + lazy val element = svgObject("bar") + lazy val level = part("level") + lazy val moveable = Seq(level) + + protected def update(value: Double) = { + translate(level, 0, (97 * (1 - value / 100)).toInt) + } + +} \ No newline at end of file diff --git a/vfd-dashboard/src/main/scala/vfd/dashboard/ui/instruments/Compass.scala b/vfd-dashboard/src/main/scala/vfd/dashboard/ui/instruments/Compass.scala new file mode 100644 index 0000000..e2d79e4 --- /dev/null +++ b/vfd-dashboard/src/main/scala/vfd/dashboard/ui/instruments/Compass.scala @@ -0,0 +1,18 @@ +package vfd.dashboard.ui.instruments + +import org.scalajs.dom.html +import vfd.dashboard.Environment + +class Compass(implicit env: Environment) extends SvgInstrument[Double] { + import SvgInstrument._ + + val initial = 0.0 + + lazy val element = svgObject("compass") + lazy val plate = part("heading") + lazy val moveable = Seq(plate) + + protected def update(heading: Double) = { + rotate(plate, heading.toInt) + } +} \ No newline at end of file diff --git a/vfd-dashboard/src/main/scala/vfd/dashboard/ui/instruments/Generic.scala b/vfd-dashboard/src/main/scala/vfd/dashboard/ui/instruments/Generic.scala new file mode 100644 index 0000000..c46bdcc --- /dev/null +++ b/vfd-dashboard/src/main/scala/vfd/dashboard/ui/instruments/Generic.scala @@ -0,0 +1,40 @@ +package vfd.dashboard.ui.instruments + +import org.scalajs.dom +import org.scalajs.dom.html +import vfd.dashboard.Environment + +class Generic( + min: Double, + med: Double, + max: Double, + unit: String)(implicit env: Environment) + extends SvgInstrument[Double] { + + import SvgInstrument._ + + val initial = 0.0 + + lazy val element = svgObject("generic") + lazy val handElement = part("hand") + lazy val unitElement = element.contentDocument.getElementById("unit") + lazy val valueElement = element.contentDocument.getElementById("value") + lazy val minElement = element.contentDocument.getElementById("min") + lazy val medElement = element.contentDocument.getElementById("med") + lazy val maxElement = element.contentDocument.getElementById("max") + lazy val moveable = Seq(handElement) + + override protected def load(e: dom.Event) = { + unitElement.textContent = unit + minElement.textContent = min.toString + medElement.textContent = med.toString + maxElement.textContent = max.toString + update(min) + super.load(e) + } + + protected def update(value: Double) = { + rotate(handElement, (value * 270 / (max - min)).toInt) + valueElement.textContent = value.toString + } +} \ No newline at end of file diff --git a/vfd-dashboard/src/main/scala/vfd/dashboard/ui/instruments/Horizon.scala b/vfd-dashboard/src/main/scala/vfd/dashboard/ui/instruments/Horizon.scala new file mode 100644 index 0000000..2484561 --- /dev/null +++ b/vfd-dashboard/src/main/scala/vfd/dashboard/ui/instruments/Horizon.scala @@ -0,0 +1,20 @@ +package vfd.dashboard.ui.instruments + +import org.scalajs.dom.html +import vfd.dashboard.Environment + +class Horizon(implicit env: Environment) extends SvgInstrument[(Double, Double)] { + import SvgInstrument._ + + val initial = (0.0, 0.0) + + lazy val element = svgObject("horizon") + lazy val pitch = part("pitch") + lazy val roll = part("roll") + lazy val moveable = Seq(pitch, roll) + + protected def update(pitchRoll: (Double, Double)) = { + translate(pitch, 0, pitchRoll._1.toInt) + rotate(roll, pitchRoll._2.toInt) + } +} diff --git a/vfd-dashboard/src/main/scala/vfd/dashboard/ui/instruments/Instrument.scala b/vfd-dashboard/src/main/scala/vfd/dashboard/ui/instruments/Instrument.scala new file mode 100644 index 0000000..5b0a13d --- /dev/null +++ b/vfd-dashboard/src/main/scala/vfd/dashboard/ui/instruments/Instrument.scala @@ -0,0 +1,28 @@ +package vfd.dashboard.ui.instruments + +import rx._ +import org.scalajs.dom.html + +/** Common trait to all flight instruments. */ +trait Instrument[A] { + + /** Initial value. */ + val initial: A + + /** Current value that is displayed in the instrument. */ + val value: Var[A] = Var(initial) + + /** HTML element that contains the rendered instrument */ + val element: html.Element + + /** Performs the actual UI update of this instrument. */ + protected def update(newValue: A): Unit + + /** Call when instrument has finished setting up its UI. */ + protected def ready() = { + Obs(value, skipInitial = true) { + update(value()) + } + } + +} \ No newline at end of file diff --git a/vfd-dashboard/src/main/scala/vfd/dashboard/ui/instruments/Led.scala b/vfd-dashboard/src/main/scala/vfd/dashboard/ui/instruments/Led.scala new file mode 100644 index 0000000..4173f37 --- /dev/null +++ b/vfd-dashboard/src/main/scala/vfd/dashboard/ui/instruments/Led.scala @@ -0,0 +1,18 @@ +package vfd.dashboard.ui.instruments + +import scalatags.JsDom.all._ +import vfd.dashboard.Environment + +class Led(implicit env: Environment) extends SvgInstrument[String] { + + val initial = "none" + + lazy val element = `object`(`type` := "image/svg+xml", "data".attr := env.asset("images/leds/led.svg"), width := 100.pct)( + "Error loading led.").render + protected def moveable = Seq() + + protected def update(color: String) = { + part("light").setAttribute("fill", color) + } + +} \ No newline at end of file diff --git a/vfd-dashboard/src/main/scala/vfd/dashboard/ui/instruments/SvgInstrument.scala b/vfd-dashboard/src/main/scala/vfd/dashboard/ui/instruments/SvgInstrument.scala new file mode 100644 index 0000000..c639cec --- /dev/null +++ b/vfd-dashboard/src/main/scala/vfd/dashboard/ui/instruments/SvgInstrument.scala @@ -0,0 +1,54 @@ +package vfd.dashboard.ui.instruments + + +import org.scalajs.dom +import org.scalajs.dom.html + +import scalatags.JsDom.all._ + +import vfd.dashboard.Environment + +/** An instrument backed by an SVG image. */ +trait SvgInstrument[A] extends Instrument[A] { + + /** SVG object element that contains the rendered instrument */ + val element: html.Object + + /** Retrieves an element of the underlying SVG document by ID. */ + protected def part(id: String) = element.contentDocument.getElementById(id).asInstanceOf[html.Element] + + /** Movable parts of the instrument */ + protected def moveable: Seq[html.Element] + + /** Called when element has been loaded. */ + protected def load(event: dom.Event): Unit = { + for (part <- moveable) { + part.style.transition = "transform 250ms ease-out" + } + ready() + } + + element.addEventListener("load", (e: dom.Event) => load(e)) +} + +/** Contains helpers for SVG instruments. */ +object SvgInstrument { + + /** Retrieves an SVG object element by its instrument's name. */ + def svgObject(name: String)(implicit app: Environment): html.Object = { + val path = app.asset("images/instruments/" + name + ".svg") + `object`(`type` := "image/svg+xml", "data".attr := path, width := 100.pct)( + "Error loading instrument " + name).render + } + + /** Applies translation styling to an element. */ + def translate(elem: html.Element, x: Int, y: Int): Unit = { + elem.style.transform = "translate(" + x + "px, " + y + "px)"; + } + + /** Applies rotation styling to an element. */ + def rotate(elem: html.Element, deg: Int): Unit = { + elem.style.transform = "rotateZ(" + deg + "deg)"; + } + +} \ No newline at end of file diff --git a/vfd-dashboard/src/main/scala/vfd/dashboard/ui/panels/Communication.scala b/vfd-dashboard/src/main/scala/vfd/dashboard/ui/panels/Communication.scala deleted file mode 100644 index c9a5c90..0000000 --- a/vfd-dashboard/src/main/scala/vfd/dashboard/ui/panels/Communication.scala +++ /dev/null @@ -1,113 +0,0 @@ -package vfd.dashboard.ui.panels - -import org.mavlink.messages.Heartbeat -import org.mavlink.messages.Motor -import org.mavlink.messages.Power -import org.scalajs.dom.html -import rx.core.Obs -import scalatags.JsDom.all.bindNode -import scalatags.JsDom.all.`class` -import scalatags.JsDom.all.div -import scalatags.JsDom.all.i -import scalatags.JsDom.all.stringAttr -import scalatags.JsDom.all.stringFrag -import scalatags.JsDom.all.stringPixelStyle -import scalatags.JsDom.all.style -import scalatags.JsDom.all.table -import scalatags.JsDom.all.tbody -import scalatags.JsDom.all.td -import scalatags.JsDom.all.thead -import scalatags.JsDom.all.tr -import scalatags.JsDom.all.width -import vfd.dashboard.Environment -import vfd.dashboard.MavlinkSocket -import vfd.dashboard.ui.components.Balance -import vfd.dashboard.ui.components.Bar -import vfd.dashboard.ui.components.Generic -import vfd.dashboard.ui.components.Led -import scalatags.jsdom.Frag - -object Communication { - - def apply(socket: MavlinkSocket)(implicit app: Environment): Frag = { - - val hb = i(`class` := "fa fa-heart heartbeat").render - - def foo() = { - hb.textContent = "" - } - - val motor0 = new Generic(0, 50, 100, "%") - val motor1 = new Generic(0, 50, 100, "%") - val motor2 = new Generic(0, 50, 100, "%") - val motor3 = new Generic(0, 50, 100, "%") - val powerDistribution = new Balance() - val batteryLevel = new Bar() - - Obs(socket.message, skipInitial = true) { - socket.message() match { - case Motor(m0, m1, m2, m3) => - motor0.update(m0) - motor1.update(m1) - motor2.update(m2) - motor3.update(m3) - powerDistribution.update(m0, m1, m2, m3) - - case Power(mV) => - batteryLevel.update(100 * (mV - 9600) / 12600) - case Heartbeat(_) => { - hb.style.visibility = "hidden" - hb.style.visibility = "visible" - //hb.classList.remove("heartbeat") - //hb.offsetHeight - //hb.classList.add("heartbeat") - } - case _ => - } - } - - div( - table(`class` := "table")( - thead("Communication"), - tbody( - tr( - td("Conn"), - div(width := "20px")(td((new Led()).element)), - td("Server"), - td("5 ms")), - tr( - td("Uplink"), - td("-20 dBm"), - td("Heartbeat"), - td(hb)))), - table(`class` := "table-instrument", style := "height: 100px")( - tbody( - tr( - td(), - td(), - td(), - td(), - td(), - td(), - td(), - td(), - td(), - td(batteryLevel.element)))), - table (`class` := "table-instrument")( - thead("Motors"), - tbody( - tr( - td(motor0.element), - td(), - td(motor1.element)), - tr( - td(), - td(powerDistribution.element), - td()), - tr( - td(motor2.element), - td(), - td(motor3.element))))) - } - -} \ No newline at end of file diff --git a/vfd-dashboard/src/main/scala/vfd/dashboard/ui/panels/Primary.scala b/vfd-dashboard/src/main/scala/vfd/dashboard/ui/panels/Primary.scala deleted file mode 100644 index 5308b40..0000000 --- a/vfd-dashboard/src/main/scala/vfd/dashboard/ui/panels/Primary.scala +++ /dev/null @@ -1,46 +0,0 @@ -package vfd.dashboard.ui.panels - -import org.mavlink.messages.Attitude -import org.scalajs.dom.html -import rx.core.Obs -import scalatags.JsDom.all.bindNode -import scalatags.JsDom.all.`class` -import scalatags.JsDom.all.stringAttr -import scalatags.JsDom.all.table -import scalatags.JsDom.all.tbody -import scalatags.JsDom.all.td -import scalatags.JsDom.all.tr -import vfd.dashboard.Environment -import vfd.dashboard.MavlinkSocket -import vfd.dashboard.ui.components.Altimeter -import vfd.dashboard.ui.components.Compass -import vfd.dashboard.ui.components.Horizon -import scalatags.jsdom.Frag - -object Primary { - - def apply(socket: MavlinkSocket)(implicit env: Environment): Frag = { - - val compass = new Compass - val horizon = new Horizon - val altimeter = new Altimeter - - Obs(socket.message, skipInitial = true) { - socket.message() match { - case Attitude(roll, pitch, yaw) => - horizon.update(pitch, roll) - compass.update(yaw) - case _ => () - } - } - - table(`class` := "table-instrument")( - tbody( - tr( - td(compass.element), - td(horizon.element), - td(altimeter.element)))) - - } - -} \ No newline at end of file -- cgit v1.2.3