mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
199 lines
5.7 KiB
Plaintext
199 lines
5.7 KiB
Plaintext
module SketchToolbar = {
|
|
@module("./SketchToolbar.jsx") @react.component
|
|
external make: (
|
|
~state: UseSketch.sketchState,
|
|
~onSetTool: UseSketch.tool => unit,
|
|
~onSetColor: UseSketch.color => unit,
|
|
~onClear: unit => unit,
|
|
~onSave: unit => unit,
|
|
~onClose: unit => unit,
|
|
) => React.element = "default"
|
|
}
|
|
|
|
type point = {x: float, y: float}
|
|
|
|
@val external document: {..} = "document"
|
|
external asCanvas: Dom.element => {..} = "%identity"
|
|
|
|
@react.component
|
|
let make = (
|
|
~isActive: bool,
|
|
~tool: UseSketch.tool,
|
|
~color: UseSketch.color,
|
|
~onSave: unit => unit,
|
|
~onClear: unit => unit,
|
|
~onClose: unit => unit,
|
|
~onSetTool: UseSketch.tool => unit,
|
|
~onSetColor: UseSketch.color => unit,
|
|
) => {
|
|
let canvasRef: React.ref<Nullable.t<Dom.element>> = React.useRef(Nullable.null)
|
|
let isDrawing = React.useRef(false)
|
|
let lastPoint = React.useRef(None)
|
|
|
|
React.useEffect1(() => {
|
|
switch canvasRef.current->Nullable.toOption {
|
|
| None => ()
|
|
| Some(el) =>
|
|
let canvas = asCanvas(el)
|
|
let parent = canvas["parentElement"]
|
|
canvas["width"] = parent["offsetWidth"]
|
|
canvas["height"] = parent["offsetHeight"]
|
|
}
|
|
None
|
|
}, [isActive])
|
|
|
|
let getPos = (e: JsxEvent.Mouse.t, canvas) => {
|
|
let rect = canvas["getBoundingClientRect"]()
|
|
{
|
|
x: Float.fromInt(JsxEvent.Mouse.clientX(e)) -. rect["left"],
|
|
y: Float.fromInt(JsxEvent.Mouse.clientY(e)) -. rect["top"],
|
|
}
|
|
}
|
|
|
|
let startDraw = (e: JsxEvent.Mouse.t) => {
|
|
switch canvasRef.current->Nullable.toOption {
|
|
| None => ()
|
|
| Some(el) =>
|
|
let canvas = asCanvas(el)
|
|
isDrawing.current = true
|
|
let pos = getPos(e, canvas)
|
|
lastPoint.current = Some(pos)
|
|
if tool === UseSketch.Highlighter {
|
|
let ctx = canvas["getContext"]("2d")
|
|
let hex = UseSketch.colorToHex(color)
|
|
let width = UseSketch.toolToWidth(tool, 2.0)
|
|
ctx["globalAlpha"] = 0.25
|
|
ctx["globalCompositeOperation"] = "source-over"
|
|
ctx["strokeStyle"] = hex
|
|
ctx["lineWidth"] = width
|
|
ctx["lineCap"] = "round"
|
|
ctx["lineJoin"] = "round"
|
|
let _ = ctx["beginPath"]()
|
|
let _ = ctx["moveTo"](pos.x, pos.y)
|
|
} else if tool === UseSketch.Text {
|
|
let text = canvas["ownerDocument"]["defaultView"]["prompt"]("Enter text:", "")
|
|
switch Nullable.toOption(text) {
|
|
| None => ()
|
|
| Some(t) =>
|
|
let ctx = canvas["getContext"]("2d")
|
|
let hex = UseSketch.colorToHex(color)
|
|
ctx["globalAlpha"] = 1.0
|
|
ctx["fillStyle"] = hex
|
|
ctx["font"] = "bold 16px sans-serif"
|
|
let _ = ctx["fillText"](t, pos.x, pos.y)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let draw = (e: JsxEvent.Mouse.t) => {
|
|
if isDrawing.current && tool !== UseSketch.Text {
|
|
switch canvasRef.current->Nullable.toOption {
|
|
| None => ()
|
|
| Some(el) =>
|
|
let canvas = asCanvas(el)
|
|
switch lastPoint.current {
|
|
| None => ()
|
|
| Some(last) =>
|
|
let ctx = canvas["getContext"]("2d")
|
|
let pos = getPos(e, canvas)
|
|
let hex = UseSketch.colorToHex(color)
|
|
let width = UseSketch.toolToWidth(tool, 2.0)
|
|
switch tool {
|
|
| UseSketch.Highlighter =>
|
|
let _ = ctx["lineTo"](pos.x, pos.y)
|
|
let _ = ctx["stroke"]()
|
|
| _ =>
|
|
ctx["globalAlpha"] = 1.0
|
|
ctx["strokeStyle"] = hex
|
|
ctx["lineWidth"] = width
|
|
ctx["lineCap"] = "round"
|
|
ctx["lineJoin"] = "round"
|
|
let _ = ctx["beginPath"]()
|
|
let _ = ctx["moveTo"](last.x, last.y)
|
|
let _ = ctx["quadraticCurveTo"](
|
|
last.x +. (pos.x -. last.x) /. 2.0,
|
|
last.y +. (pos.y -. last.y) /. 2.0,
|
|
pos.x,
|
|
pos.y,
|
|
)
|
|
let _ = ctx["stroke"]()
|
|
}
|
|
lastPoint.current = Some(pos)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let stopDraw = (_e: JsxEvent.Mouse.t) => {
|
|
if tool === UseSketch.Highlighter {
|
|
switch canvasRef.current->Nullable.toOption {
|
|
| None => ()
|
|
| Some(el) =>
|
|
let canvas = asCanvas(el)
|
|
let ctx = canvas["getContext"]("2d")
|
|
let _ = ctx["stroke"]()
|
|
ctx["globalAlpha"] = 1.0
|
|
ctx["globalCompositeOperation"] = "source-over"
|
|
}
|
|
}
|
|
isDrawing.current = false
|
|
lastPoint.current = None
|
|
}
|
|
|
|
let handleClear = () => {
|
|
switch canvasRef.current->Nullable.toOption {
|
|
| None => ()
|
|
| Some(el) =>
|
|
let canvas = asCanvas(el)
|
|
let ctx = canvas["getContext"]("2d")
|
|
let _ = ctx["clearRect"](0, 0, canvas["width"], canvas["height"])
|
|
onClear()
|
|
}
|
|
}
|
|
|
|
let handleSave = () => {
|
|
switch canvasRef.current->Nullable.toOption {
|
|
| None => ()
|
|
| Some(el) =>
|
|
let canvas = asCanvas(el)
|
|
let dataUrl = canvas["toDataURL"]("image/png")
|
|
let link = document["createElement"]("a")
|
|
link["href"] = dataUrl
|
|
link["download"] = "sketch.png"
|
|
let _ = link["click"]()
|
|
onSave()
|
|
}
|
|
}
|
|
|
|
if !isActive {
|
|
React.null
|
|
} else {
|
|
<div className="absolute inset-0 z-10" style={{pointerEvents: isActive ? "all" : "none"}}>
|
|
<SketchToolbar.make
|
|
state={{
|
|
UseSketch.isActive,
|
|
tool,
|
|
color,
|
|
strokeWidth: 2.0,
|
|
}}
|
|
onSetTool={onSetTool}
|
|
onSetColor={onSetColor}
|
|
onClear={handleClear}
|
|
onSave={handleSave}
|
|
onClose={onClose}
|
|
/>
|
|
<canvas
|
|
ref={canvasRef->ReactDOM.Ref.domRef}
|
|
width="100%"
|
|
height="100%"
|
|
className="w-full h-full cursor-crosshair"
|
|
onMouseDown={startDraw}
|
|
onMouseMove={draw}
|
|
onMouseUp={stopDraw}
|
|
onMouseLeave={stopDraw}
|
|
/>
|
|
</div>
|
|
}
|
|
}
|