js,jsdom: Canvas & context API; Added TypeSymbol.is_js_compatible & temporary hacks for JS ifaces (#12526)

pull/12533/head
playX 2021-11-20 22:28:11 +03:00 committed by GitHub
parent 258d0d6df7
commit 243e66a106
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 343 additions and 71 deletions

View File

@ -1,82 +1,90 @@
/*
import jsdom
fn get_2dcontext(canvas jsdom.IElement) ?jsdom.CanvasRenderingContext2D {
if canvas is jsdom.HTMLCanvasElement {
c := canvas.get_context('2d')
match c {
jsdom.CanvasRenderingContext2D {
return c
}
else {
return error('cannot fetch 2d context')
}
fn get_canvas(elem JS.HTMLElement) &JS.HTMLCanvasElement {
match elem {
JS.HTMLCanvasElement {
return elem
}
else {
panic('Not a canvas')
}
} else {
return error('canvas is not an HTMLCanvasElement')
}
}
fn draw_line(context jsdom.CanvasRenderingContext2D, x1 int, y1 int, x2 int, y2 int) {
context.begin_path()
context.set_stroke_style('black')
context.set_line_width(1)
context.move_to(x1, y1)
context.line_to(x2, y2)
fn draw_line(mut context JS.CanvasRenderingContext2D, x1 int, y1 int, x2 int, y2 int) {
context.beginPath()
context.strokeStyle = 'black'.str
context.lineWidth = JS.Number(1)
context.moveTo(x1, y1)
context.lineTo(x2, y2)
context.stroke()
context.close_path()
context.closePath()
}
struct DrawState {
mut:
context JS.CanvasRenderingContext2D
drawing bool
x int
y int
}
fn main() {
window := jsdom.window()
document := jsdom.document
elem := document.get_element_by_id('myButton') ?
elemc := document.get_element_by_id('myCanvas') or { panic('no canvas') }
canv := jsdom.get_html_canvas_element(elemc) or { panic('expected canvas') }
context := canv.get_context_2d()
mut state := DrawState{}
canv.add_event_listener('mousedown', fn [mut state] (_ jsdom.IEventTarget, event jsdom.IEvent) {
state.drawing = true
if event is jsdom.MouseEvent {
state.x = event.offset_x()
state.y = event.offset_y()
clear_btn := document.getElementById('clearButton'.str) ?
canvas_elem := document.getElementById('canvas'.str) ?
canvas := get_canvas(canvas_elem)
ctx := canvas.getContext('2d'.str, js_undefined()) ?
context := match ctx {
JS.CanvasRenderingContext2D {
ctx
}
})
else {
panic('can not get 2d context')
}
}
mut state := DrawState{context, false, 0, 0}
canv.add_event_listener('mousemove', fn [context, mut state] (_ jsdom.IEventTarget, event jsdom.IEvent) {
canvas.addEventListener('mousedown'.str, fn [mut state] (event JS.Event) {
state.drawing = true
match event {
JS.MouseEvent {
state.x = int(event.offsetX)
state.y = int(event.offsetY)
}
else {}
}
}, JS.EventListenerOptions{})
canvas.addEventListener('mousemove'.str, fn [mut state] (event JS.Event) {
if state.drawing {
if event is jsdom.MouseEvent {
draw_line(context, state.x, state.y, event.offset_x(), event.offset_y())
state.x = event.offset_x()
state.y = event.offset_y()
match event {
JS.MouseEvent {
draw_line(mut state.context, state.x, state.y, int(event.offsetX),
int(event.offsetY))
state.x = int(event.offsetX)
state.y = int(event.offsetY)
}
else {}
}
}
})
}, JS.EventListenerOptions{})
jsdom.window.add_event_listener('mouseup', fn [context, mut state] (_ jsdom.IEventTarget, event jsdom.IEvent) {
window.addEventListener('mouseup'.str, fn [mut state] (event JS.Event) {
if state.drawing {
if event is jsdom.MouseEvent {
draw_line(context, state.x, state.y, event.offset_x(), event.offset_y())
match event {
JS.MouseEvent {
draw_line(mut state.context, state.x, state.y, int(event.offsetX),
int(event.offsetY))
}
else {}
}
state.x = 0
state.y = 0
state.drawing = false
}
})
elem.add_event_listener('click', fn [context, canv] (_ jsdom.IEventTarget, _ jsdom.IEvent) {
context.clear_rect(0, 0, canv.width(), canv.height())
})
}
*/
fn main() {
panic('jsdom is being refactored; This example will be available soon')
}, JS.EventListenerOptions{})
clear_btn.addEventListener('click'.str, fn [mut state, canvas] (_ JS.Event) {
state.context.clearRect(0, 0, canvas.width, canvas.height)
}, JS.EventListenerOptions{})
}

View File

@ -1,7 +1,7 @@
<body class="main">
<title>Drawing with mouse events</title>
<input type="button" id="myButton" value="Clear canvas">
<canvas style="border: 1px solid black;" width="720" height="480" id="myCanvas"></canvas>
<input type="button" id="clearButton" value="Clear canvas">
<canvas style="border: 1px solid black;" width="720" height="480" id="canvas"></canvas>
<script type="text/javascript" src="draw.js"></script>
</body>

View File

@ -57,6 +57,34 @@ pub interface JS.Map {
pub interface JS.Any {}
pub fn js_is_null(x JS.Any) bool {
res := false
#res.val = x === null
return res
}
pub fn js_is_undefined(x JS.Any) bool {
res := false
#res.val = x === undefined
return res
}
pub fn js_null() JS.Any {
mut obj := JS.Any{}
#obj = null;
return obj
}
pub fn js_undefined() JS.Any {
mut obj := JS.Any{}
#obj = undefined;
return obj
}
pub interface JS.Array {
JS.Any // map(fn (JS.Any) JS.Any) JS.Array
map(JS.Any) JS.Array

View File

@ -8,6 +8,13 @@ pub mut:
will_read_frequently bool
}
pub fn (settings CanvasRenderingContext2DSettings) to_js() JS.Any {
mut object := JS.Any{}
#object = { alpha: settings.alpha, colorSpace: settings.color_space.str, desynchronized: settings.desynchronized.val, willReadFrequently: settings.will_read_frequently.val };
return object
}
pub interface JS.DOMMatrix2DInit {
mut:
a JS.Number
@ -168,9 +175,9 @@ pub struct JS.EventListenerOptions {
}
pub interface JS.EventTarget {
addEventListener(cb EventCallback, options JS.EventListenerOptions)
addEventListener(event JS.String, cb EventCallback, options JS.EventListenerOptions)
dispatchEvent(event JS.Event) JS.Boolean
removeEventListener(cb EventCallback, options JS.EventListenerOptions)
removeEventListener(event JS.String, cb EventCallback, options JS.EventListenerOptions)
}
// Event is an event which takes place in the DOM.
@ -320,6 +327,7 @@ pub interface JS.Document {
lastModified JS.String
inputEncoding JS.String
implementation JS.DOMImplementation
getElementById(id JS.String) ?JS.HTMLElement
mut:
bgColor JS.String
body JS.HTMLElement
@ -368,6 +376,14 @@ pub interface JS.Element {
scroll(x JS.Number, y JS.Number)
scrollBy(x JS.Number, y JS.Number)
toggleAttribute(qualifiedName JS.String, force JS.Boolean) JS.Boolean
getElementsByClassName(className JS.String) JS.HTMLCollection
getElementsByTagName(qualifiedName JS.String) JS.HTMLCollection
getEelementsByTagNameNS(namespaecURI JS.String, localName JS.String) JS.HTMLCollection
hasAttribute(qualifiedName JS.String) JS.Boolean
hasAttributeNS(namespace JS.String, localName JS.String) JS.Boolean
hasAttributes() JS.Boolean
hasPointerCapture(pointerId JS.Number) JS.Boolean
matches(selectors JS.String) JS.Boolean
mut:
className JS.String
id JS.String
@ -383,6 +399,13 @@ pub const (
document = JS.Document{}
)
pub fn window() JS.Window {
mut x := JS.Any(voidptr(0))
#x = window;
return x
}
fn init() {
#jsdom__document = document;
}
@ -398,3 +421,143 @@ pub fn event_listener(callback fn (JS.EventTarget, JS.Event)) EventCallback {
callback(target, event)
}
}
pub interface JS.HTMLCollection {
length JS.Number
item(idx JS.Number) ?JS.Any
namedItem(name JS.String) ?JS.Any
}
pub interface JS.HTMLElement {
JS.Element
accessKeyLabel JS.String
offsetHeight JS.Number
offsetLeft JS.Number
offsetParent JS.Any
offsetTop JS.Number
offsetWidth JS.Number
click()
mut:
accessKey JS.String
autocapitalize JS.String
dir JS.String
draggable JS.Boolean
hidden JS.Boolean
innerText JS.String
lang JS.String
outerText JS.String
spellcheck JS.Boolean
title JS.String
translate JS.Boolean
}
pub fn JS.HTMLElement.prototype.constructor() JS.HTMLElement
pub interface JS.HTMLEmbedElement {
getSVGDocument() ?JS.Document
mut:
align JS.String
height JS.String
src JS.String
width JS.String
}
pub fn html_embed_type(embed JS.HTMLEmbedElement) JS.String {
mut str := JS.String{}
#str = embed.type
return str
}
pub fn JS.HTMLEmbedElement.prototype.constructor() JS.HTMLEmbedElement
pub type CanvasContext = JS.CanvasRenderingContext2D
| JS.WebGL2RenderingContext
| JS.WebGLRenderingContext
pub interface JS.HTMLCanvasElement {
JS.HTMLElement
getContext(contextId JS.String, options JS.Any) ?CanvasContext
mut:
height JS.Number
width JS.Number
}
pub type FillStyle = JS.CanvasGradient | JS.CanvasPattern | JS.String
pub interface JS.CanvasRenderingContext2D {
canvas JS.HTMLCanvasElement
beginPath()
clip(path JS.Path2D, fillRule JS.String)
fill(path JS.Path2D, fillRule JS.String)
isPointInPath(path JS.Path2D, x JS.Number, y JS.Number, fillRule JS.String) JS.Boolean
isPointInStroke(path JS.Path2D, x JS.Number, y JS.Number) JS.Boolean
stoke(path JS.Path2D)
createLinearGradient(x0 JS.Number, y0 JS.Number, x1 JS.Number, y1 JS.Number) JS.CanvasGradient
createRadialGradient(x0 JS.Number, y0 JS.Number, r0 JS.Number, x1 JS.Number, y1 JS.Number, r1 JS.Number) JS.CanvasGradient
createPattern(image JS.CanvasImageSource, repetition JS.String) ?JS.CanvasPattern
arc(x JS.Number, y JS.Number, radius JS.Number, startAngle JS.Number, endAngle JS.Number, counterclockwise JS.Boolean)
arcTo(x1 JS.Number, y1 JS.Number, x2 JS.Number, y2 JS.Number, radius JS.Number)
bezierCurveTo(cp1x JS.Number, cp1y JS.Number, cp2x JS.Number, cp2y JS.Number, x JS.Number, y JS.Number)
closePath()
ellipse(x JS.Number, y JS.Number, radiusX JS.Number, radiusY JS.Number, rotation JS.Number, startAngle JS.Number, endAngle JS.Number, counterclockwise JS.Boolean)
lineTo(x JS.Number, y JS.Number)
moveTo(x JS.Number, y JS.Number)
quadraticCurveTo(cpx JS.Number, cpy JS.Number, x JS.Number, y JS.Number)
rect(x JS.Number, y JS.Number, w JS.Number, h JS.Number)
getLineDash() JS.Array
setLineDash(segments JS.Array)
clearRect(x JS.Number, y JS.Number, w JS.Number, h JS.Number)
fillRect(x JS.Number, y JS.Number, w JS.null, h JS.Number)
strokeRect(x JS.Number, y JS.Number, w JS.Number, h JS.Number)
getTransformt() JS.DOMMatrix
resetTransform()
rotate(angle JS.Number)
scale(x JS.Number, y JS.Number)
setTransform(matrix JS.DOMMatrix)
transform(a JS.Number, b JS.Number, c JS.Number, d JS.Number, e JS.Number, f JS.Number)
translate(x JS.Number, y JS.Number)
drawFocusIfNeeded(path JS.Path2D, element JS.Element)
stroke()
mut:
lineCap JS.String
lineDashOffset JS.Number
lineJoin JS.String
lineWidth JS.Number
miterLimit JS.Number
fillStyle FillStyle
strokeStyle FillStyle
globalAlpha JS.Number
globalCompositeOperation JS.String
}
pub interface JS.CanvasGradient {
addColorStop(offset JS.Number, color JS.String)
}
pub interface JS.CanvasPattern {
setTransform(transform JS.DOMMatrix)
}
pub interface JS.WebGLRenderingContext {
canvas JS.HTMLCanvasElement
drawingBufferHeight JS.Number
drawingBufferWidth JS.Number
}
pub interface JS.WebGL2RenderingContext {
JS.WebGLRenderingContext
}
pub interface JS.Window {
JS.EventTarget
closed JS.Boolean
devicePixelRatio JS.Number
document JS.Document
frameElement JS.Element
innerHeight JS.Number
innerWidth JS.Number
length JS.Number
}
pub interface JS.Path2D {}

View File

@ -267,7 +267,12 @@ pub fn (t &Table) is_same_method(f &Fn, func &Fn) string {
for i in 0 .. f.params.len {
// don't check receiver for `.typ`
has_unexpected_type := i > 0 && f.params[i].typ != func.params[i].typ
// temporary hack for JS ifaces
lsym := t.get_type_symbol(f.params[i].typ)
rsym := t.get_type_symbol(func.params[i].typ)
if lsym.language == .js && rsym.language == .js {
return ''
}
has_unexpected_mutability := !f.params[i].is_mut && func.params[i].is_mut
if has_unexpected_type || has_unexpected_mutability {

View File

@ -1247,6 +1247,34 @@ pub fn (t &TypeSymbol) find_method_with_generic_parent(name string) ?Fn {
return none
}
// is_js_compatible returns true if type can be converted to JS type and from JS type back to V type
pub fn (t &TypeSymbol) is_js_compatible() bool {
mut table := global_table
if t.kind == .void {
return true
}
if t.kind == .function {
return true
}
if t.language == .js || t.name.starts_with('JS.') {
return true
}
match t.info {
SumType {
for variant in t.info.variants {
sym := table.get_final_type_symbol(variant)
if !sym.is_js_compatible() {
return false
}
}
return true
}
else {
return true
}
}
}
pub fn (t &TypeSymbol) str_method_info() (bool, bool, int) {
mut has_str_method := false
mut expects_ptr := false

View File

@ -413,8 +413,10 @@ pub fn (mut c Checker) sum_type_decl(node ast.SumTypeDecl) {
variant.pos)
} else if sym.kind in [.placeholder, .int_literal, .float_literal] {
c.error('unknown type `$sym.name`', variant.pos)
} else if sym.kind == .interface_ {
} else if sym.kind == .interface_ && sym.language != .js {
c.error('sum type cannot hold an interface', variant.pos)
} else if sym.kind == .struct_ && sym.language == .js {
c.error('sum type cannot hold an JS struct', variant.pos)
}
if sym.name.trim_prefix(sym.mod + '.') == node.name {
c.error('sum type cannot hold itself', variant.pos)
@ -555,8 +557,7 @@ pub fn (mut c Checker) interface_decl(mut node ast.InterfaceDecl) {
c.ensure_type_exists(method.return_type, method.return_type_pos) or { return }
if is_js {
mtyp := c.table.get_type_symbol(method.return_type)
if (mtyp.language != .js && !method.return_type.is_void())
&& !mtyp.name.starts_with('JS.') {
if !mtyp.is_js_compatible() {
c.error('method $method.name returns non JS type', method.pos)
}
}
@ -567,8 +568,7 @@ pub fn (mut c Checker) interface_decl(mut node ast.InterfaceDecl) {
c.ensure_type_exists(param.typ, param.pos) or { return }
if is_js {
ptyp := c.table.get_type_symbol(param.typ)
if (ptyp.kind != .function && ptyp.language != .js
&& !ptyp.name.starts_with('JS.')) && !(j == method.params.len - 1
if !ptyp.is_js_compatible() && !(j == method.params.len - 1
&& method.is_variadic) {
c.error('method `$method.name` accepts non JS type as parameter',
method.pos)
@ -595,7 +595,7 @@ pub fn (mut c Checker) interface_decl(mut node ast.InterfaceDecl) {
c.ensure_type_exists(field.typ, field.pos) or { return }
if is_js {
tsym := c.table.get_type_symbol(field.typ)
if tsym.language != .js && !tsym.name.starts_with('JS.') && tsym.kind != .function {
if !tsym.is_js_compatible() {
c.error('field `$field.name` uses non JS type', field.pos)
}
}
@ -5313,6 +5313,7 @@ pub fn (mut c Checker) expr(node ast.Expr) ast.Type {
defer {
c.expr_level--
}
// c.expr_level set to 150 so that stack overflow does not occur on windows
if c.expr_level > 150 {
c.error('checker: too many expr levels: $c.expr_level ', node.position())
@ -6407,6 +6408,7 @@ fn (mut c Checker) match_exprs(mut node ast.MatchExpr, cond_type_sym ast.TypeSym
} else {
expr_type = expr_types[0].typ
}
c.smartcast(node.cond, node.cond_type, expr_type, mut branch.scope)
}
}

View File

@ -620,8 +620,11 @@ fn (mut g JsGen) gen_method_decl(it ast.FnDecl, typ FnGenType) {
if is_varg {
g.writeln('$arg_name = new array(new array_buffer({arr: $arg_name,len: new int(${arg_name}.length),index_start: new int(0)}));')
} else {
if arg.typ.is_ptr() || arg.is_mut {
g.writeln('$arg_name = new \$ref($arg_name)')
asym := g.table.get_type_symbol(arg.typ)
if asym.kind != .interface_ && asym.language != .js {
if arg.typ.is_ptr() || arg.is_mut {
g.writeln('$arg_name = new \$ref($arg_name)')
}
}
}
}
@ -743,7 +746,9 @@ fn (mut g JsGen) gen_anon_fn(mut fun ast.AnonFn) {
if is_varg {
g.writeln('$arg_name = new array(new array_buffer({arr: $arg_name,len: new int(${arg_name}.length),index_start: new int(0)}));')
} else {
if arg.typ.is_ptr() || arg.is_mut {
asym := g.table.get_type_symbol(arg.typ)
if arg.typ.is_ptr() || (arg.is_mut && asym.kind != .interface_ && asym.language != .js) {
g.writeln('$arg_name = new \$ref($arg_name)')
}
}

View File

@ -2343,20 +2343,53 @@ fn (mut g JsGen) match_expr_sumtype(node ast.MatchExpr, is_expr bool, cond_var M
} else {
g.write('if (')
}
if sym.kind == .sum_type || sym.kind == .interface_ {
x := branch.exprs[sumtype_index]
if x is ast.TypeNode {
typ := g.unwrap_generic(x.typ)
tsym := g.table.get_type_symbol(typ)
if tsym.language == .js && (tsym.name == 'JS.Number'
|| tsym.name == 'JS.Boolean' || tsym.name == 'JS.String') {
g.write('typeof ')
}
}
}
g.match_cond(cond_var)
if sym.kind == .sum_type {
g.write(' instanceof ')
g.expr(branch.exprs[sumtype_index])
x := branch.exprs[sumtype_index]
if x is ast.TypeNode {
typ := g.unwrap_generic(x.typ)
tsym := g.table.get_type_symbol(typ)
if tsym.language == .js && (tsym.name == 'JS.Number'
|| tsym.name == 'JS.Boolean' || tsym.name == 'JS.String') {
g.write(' === "${tsym.name[3..].to_lower()}"')
} else {
g.write(' instanceof ')
g.expr(branch.exprs[sumtype_index])
}
} else {
g.write(' instanceof ')
g.expr(branch.exprs[sumtype_index])
}
} else if sym.kind == .interface_ {
if !sym.name.starts_with('JS.') {
g.write('.val')
}
if branch.exprs[sumtype_index] is ast.TypeNode {
g.write(' instanceof ')
g.expr(branch.exprs[sumtype_index])
x := branch.exprs[sumtype_index]
if x is ast.TypeNode {
typ := g.unwrap_generic(x.typ)
tsym := g.table.get_type_symbol(typ)
if tsym.language == .js && (tsym.name == 'Number'
|| tsym.name == 'Boolean' || tsym.name == 'String') {
g.write(' === $tsym.name.to_lower()')
} else {
g.write(' instanceof ')
g.expr(branch.exprs[sumtype_index])
}
} else {
g.write(' instanceof ')
g.write('None__')
}
}