doodle.svg.effect.Canvas.scala Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2015 Creative Scala
*
* 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.
*/
package doodle
package svg
package effect
import cats.effect.IO
import cats.effect.Resource
import cats.effect.unsafe.IORuntime
import cats.syntax.all.*
import doodle.core.BoundingBox
import doodle.core.Point
import doodle.core.Transform
import doodle.core.font.Font
import fs2.Stream
import fs2.concurrent.Topic
import org.scalajs.dom
import org.scalajs.dom.Element
import org.scalajs.dom.svg.Rect
import scalatags.JsDom
import scalatags.JsDom.svgAttrs
import scalatags.JsDom.svgTags
final case class Canvas(
target: dom.Node,
frame: Frame,
redrawTopic: Topic[IO, Int],
mouseClickTopic: Topic[IO, Point],
mouseMoveTopic: Topic[IO, Point]
) {
import JsDom.all.{Tag as _, *}
val nullCallback: Either[Throwable, Unit] => Unit = _ => ()
val algebra: Algebra =
new js.JsAlgebra(this, Svg.svgResultApplicative, Svg.svgResultApplicative)
private val eventListenerOptions = new dom.EventListenerOptions {}
eventListenerOptions.once = true
private var redrawStream: Stream[IO, Int] = _
private var mouseMoveStream: Stream[IO, Point] = _
private var mouseClickStream: Stream[IO, Point] = _
{
var started = false
var lastTs = 0.0
redrawStream = Stream.repeatEval(
IO.async_ { cb =>
dom.window.requestAnimationFrame(ts =>
if started then {
val diff = ts - lastTs
lastTs = ts
cb(Right(diff.toInt))
} else {
started = true
cb(Right(0))
}
)
()
}
)
}
private def mouseClickCallback(tx: Transform): dom.MouseEvent => Point =
(evt: dom.MouseEvent) => {
val rect = evt.target.asInstanceOf[dom.Element].getBoundingClientRect()
val x = evt.clientX - rect.left; // x position within the element.
val y = evt.clientY - rect.top;
tx(doodle.core.Point(x, y))
}
private def mouseMoveCallback(tx: Transform): dom.MouseEvent => Point =
(evt: dom.MouseEvent) => {
val rect = evt.target.asInstanceOf[dom.Element].getBoundingClientRect()
val x = evt.clientX - rect.left; // x position within the element.
val y = evt.clientY - rect.top;
tx(doodle.core.Point(x, y))
}
private def addCallbacks(elt: Element, tx: Transform): Unit = {
val onMoveCallback = mouseMoveCallback(tx)
val onMove: Stream[IO, Point] =
Stream.repeatEval(
IO.async_(cb =>
elt.addEventListener(
"move",
evt => cb(Right(onMoveCallback(evt.asInstanceOf[dom.MouseEvent]))),
eventListenerOptions
)
)
)
val onClickCallback = mouseClickCallback(tx)
val onClick: Stream[IO, Point] =
Stream.repeatEval(
IO.async_(cb =>
elt.addEventListener(
"click",
evt => cb(Right(onClickCallback(evt.asInstanceOf[dom.MouseEvent]))),
eventListenerOptions
)
)
)
mouseMoveStream = onMove
mouseClickStream = onClick
}
private var currentBB: BoundingBox = BoundingBox.empty
private var svgRoot: dom.Node = _
{
val tx = Svg.inverseClientTransform(currentBB, frame.size)
val tag = Svg.svgTag(currentBB, frame).render
addCallbacks(tag, tx)
svgRoot = tag
target.appendChild(svgRoot)
}
/** Get the root
© 2015 - 2025 Weber Informatics LLC | Privacy Policy