diff options
Diffstat (limited to 'mavigator-cockpit/src/main/scala/mavigator/cockpit/Instruments.scala')
-rw-r--r-- | mavigator-cockpit/src/main/scala/mavigator/cockpit/Instruments.scala | 173 |
1 files changed, 173 insertions, 0 deletions
diff --git a/mavigator-cockpit/src/main/scala/mavigator/cockpit/Instruments.scala b/mavigator-cockpit/src/main/scala/mavigator/cockpit/Instruments.scala new file mode 100644 index 0000000..7c4bf92 --- /dev/null +++ b/mavigator-cockpit/src/main/scala/mavigator/cockpit/Instruments.scala @@ -0,0 +1,173 @@ +package mavigator +package cockpit + +import org.scalajs.dom +import org.scalajs.dom.html + +import scalatags.JsDom.all._ + +import util.Page + +trait Instruments { page: Page => + + trait Instrument[A] { + def element: html.Element + def update(newValue: A): Unit + } + + /** Common behaviour for svg-based instruments. */ + abstract class SvgInstrument[A]( + path: String + ) extends Instrument[A] { + + override def element: html.Element = objectElement + + /** SVG object element that contains the rendered instrument */ + lazy val objectElement: html.Object = SvgInstrument.svgObject(path) + + /** Retrieves an element of the underlying SVG document by ID. */ + protected def part(id: String): html.Element = + objectElement.contentDocument.getElementById(id).asInstanceOf[html.Element] + + /** Movable parts of the instrument */ + protected def moveable: Seq[html.Element] + + /** Called when element has been loaded. */ + private def load(event: dom.Event): Unit = { + for (part <- moveable) { + part.style.transition = "transform 20ms ease-out" + } + } + + 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(path: String): html.Object = { + val fullPath = page.asset("images/" + path) + `object`(`type` := "image/svg+xml", "data".attr := fullPath, width := 100.pct)( + "Error loading instrument " + fullPath).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, rad: Double): Unit = { + elem.style.transform = "rotateZ(" + rad + "rad)"; + } + + } + + lazy val attitudeOverlay = new SvgInstrument[(Float, Float)]("hud/attitude.svg") { + override def element = div(`class`:="hud-overlay")(objectElement).render + private lazy val pitchPart = part("pitch") + private lazy val rollPart = part("roll") + override lazy val moveable = Seq(pitchPart, rollPart) + + override def update(pitchRoll: (Float, Float)) = { + import SvgInstrument._ + val (pitch, roll) = pitchRoll + translate(pitchPart, 0, (pitch * 180 / math.Pi * 10).toInt) // 1deg === 10px + rotate(rollPart, roll) + } + } + + lazy val horizonOverlay = new SvgInstrument[(Float, Float)]("hud/horizon.svg") { + import SvgInstrument._ + + override def element = div(`class`:="hud-overlay")(objectElement).render + lazy val horizon = part("horizon") + lazy override val moveable = Seq(horizon) + + override def update(pitchRoll: (Float, Float)) = { + val (pitch, roll) = pitchRoll + + val t = (pitch * 180 / math.Pi * 10).toInt // 1deg === 10px + + horizon.style.transform = s"rotateZ(${roll}rad)translate(0px, ${t}px) " + } + } + + + val overlayStyle = """ + |.hud-overlay { + | position: absolute; + | left: 0; + | right: 0; + | top: 0; + | bottom: 0; + | + | display: flex; + | flex-direction: row; + | justify-content: center; + | align-items: center; + |} + |.hud-overlay > * { + | flex: 1 1 0; + | width: 100%; + | height: 100%; + | max-width: 100%; + | max-height: 100%; + |}""".stripMargin + + + def mode(name: String, kind: String, on: Boolean = false) = { + div(`class` := s"mode $kind ${if (!on) "off"}")(name) + } + + //TODO make these into real instruments and lazy vals + def modes = div(style := "float: right;")( + mode("LINK", "danger", true), + mode("BAT", "warning", true), + mode("GPS", "warning", true), + mode("STABILIZED", "info", true) + ) + + val modeStyle = """ +.mode { + display: inline-block; + box-sizing: border-box; + text-decoration: normal; + margin-right: 5px; + padding: 5px; +} + +.mode.danger { + color: #d9534f; + text-shadow: 0 0 5px #d9534f; + animation: danger-blink 0.5s linear infinite; + -webkit-animation: danger-blink 0.5s linear infinite; +} + +.mode.warning { + color: #f0ad4e; + text-shadow: 0 0 5px #f0ad4e; +} + +.mode.info { + color: #5bc0de; + text-shadow: 0 0 5px #5bc0de; +} + +.mode.success { + color: #5cb85c; + text-shadow: 0 0 5px #5cb85c; +} + +.mode.off { + color: #e6e6e6; + text-shadow: none; + animation: none; + -webkit-animation: none; +} +""" + + def instrumentStyles: Seq[String] = Seq(overlayStyle, modeStyle) + +} |