Closed chris-praxis closed 2 years ago
CanvasTest.vue.zip This version creates a new Canvas ever time tab page is entered/loaded instead of defining via XML, but it results in the same errors when drawing canvases after the first one. Looks like a stale BufferQueue is still accessed, even though it's a new canvas.?
CanvasTest.vue 2.zip I would go about it more like
Hey can you try the latest alpha
tag an lmk
Hey can you try the latest
alpha
tag an lmk
This is happening with alpha too, simply draw a rectangle in a canvas inside of a tab using the @nativescript-community/ui-material-bottom-navigation, navigate to the next tab and back to the first one and the canvas is empty.
I tested using a (tap) event for redraw the canvas when "clicking", if I navigate to a tab and then go back to the first tab (the one with the canvas) and "click it" for call my draw function it throws GLContext: Cannot swap buffers!
, if the first time I tap the component for redraw (without navigating to another tab) it draws and no error is thrown.
I solved the problem (temporally) using loaded and unloaded from the parent component elementRef.nativeView.on
and *ngIf from Angular over the Canvas HTML element fortunately I created a canvas abstract component for that, for anyone that wants to use it, here is:
import { AfterViewInit, Component, ElementRef, ViewChild } from '@angular/core'
import { Canvas } from '@nativescript/canvas'
import { AndroidApplication, ContentView, View } from '@nativescript/core'
import { android } from '@nativescript/core/application'
import { Console } from '@shared/tools'
@Component({
template: ``
})
export abstract class CanvasBaseComponent extends ContentView implements AfterViewInit {
public showCanvas = false
@ViewChild('canvas')
set canvasRef(canvasRef: ElementRef<Canvas>) {
this._canvas = canvasRef?.nativeElement
}
private _canvas: Canvas
constructor(
readonly elementRef: ElementRef<View>
) {
super()
}
ngAfterViewInit(): void {
this.setupCanvas()
}
get canvas() {
return this._canvas
}
private setupCanvas() {
this.elementRef.nativeElement.on('loaded', () => {
this.showCanvas = true
this.onCanvasLoaded(this._canvas)
})
this.elementRef.nativeElement.on('unloaded', () => {
this.showCanvas = false
this.onUnloaded()
})
}
public destroyCanvas() {
this._canvas.disposeNativeView()
}
public onCanvasReady(event) {
const canvas = event.object as Canvas
setTimeout(() => this.onDraw(canvas))
}
public abstract onDraw(canvas: Canvas)
public onCanvasLoaded(canvas: Canvas) {
}
public onCanvasUnloaded() {
}
public redraw() {
setTimeout(() => {
if (this._canvas) {
this.onDraw(this._canvas)
}
})
}
}
Use like this:
import { AfterViewInit, Component, ElementRef, Input, OnDestroy, OnInit } from '@angular/core'
import { localizeStructureMap } from '@config'
import { registerElement } from '@nativescript/angular'
import { Canvas } from '@nativescript/canvas'
import { Color, CSSType, View } from '@nativescript/core'
import { toDevicePixels } from '@nativescript/core/utils/layout-helper'
import { CanvasBaseComponent } from '@shared/abstract-classes'
import { degreesToRadians, getViewConstraints } from '@shared/tools'
import anime from 'animejs'
const componentName = 'AppAcrDynamicKey'
@CSSType(componentName)
@Component({
selector: componentName,
templateUrl: './acr-dynamic-key.component.html',
styleUrls: ['./acr-dynamic-key.component.scss']
})
export class AcrDynamicKeyComponent extends CanvasBaseComponent implements OnInit, OnDestroy, AfterViewInit {
public localizePath = localizeStructureMap.modules.AcrModule.components.AcrDynamicKey
@Input() public timeSeconds: number = 5
@Input() public updateIntervalMillis: number = 100
@Input() public start = true
public componentName = componentName
public dyanamicKey: string
private clockTimerAnimationProps = {
// timer start angle in radians (90º)
clockTimerStartAngle: -90, // Math.PI/2,
// timer end angle in degrees
clockTimerEndAngle: -90 // Math.PI/2
}
private clockTimerAnimation: anime.AnimeInstance
constructor(
readonly elementRef: ElementRef<View>
) {
super(elementRef)
}
ngOnInit(): void {
// TODO: get real key
const generateKey = () => {
this.dyanamicKey = Math.random().toString().slice(2, 8)
}
this.clockTimerAnimation = anime({
targets: this.clockTimerAnimationProps,
clockTimerEndAngle: 270,
duration: this.timeSeconds * 1000,
autoplay: false,
loop: true,
easing: 'linear',
update: () => {
// console.log(Math.ceil(this.clockTimerAnimationProps.clockTimerEndAngle))
this.redraw()
},
loopComplete: () => {
// TODO: get real key
generateKey()
}
})
generateKey()
}
ngOnDestroy(): void {
console.log('onDestroy')
}
ngAfterViewInit(): void {
super.ngAfterViewInit()
if (this.start) {
setTimeout(() => {
this.clockTimerAnimation.play()
})
}
}
public onDraw(canvas: Canvas) {
const canvasContext = canvas.getContext('2d') as unknown as CanvasRenderingContext2D
const { width, height } = getViewConstraints(canvas as any)
const clockBorderWidth = toDevicePixels(6)
const clockX = width / 2 + toDevicePixels(5)
const clockY = height / 2
const clockRadius = height / 2 - toDevicePixels(12)
const { clockTimerStartAngle, clockTimerEndAngle } = this.clockTimerAnimationProps
canvasContext.clearRect(0, 0, width, height)
canvasContext.setTransform(1, 0, 0, 1, 0, 0)
// canvasContext.save()
canvasContext.strokeStyle = new Color("white").hex
canvasContext.fillStyle = new Color("white").hex
canvasContext.lineWidth = clockBorderWidth
canvasContext.beginPath()
// clock form
canvasContext.arc(clockX, clockY, clockRadius, 0, 2 * Math.PI)
canvasContext.stroke()
// clock button
canvasContext.moveTo(clockX, clockY - clockRadius)
canvasContext.fillRect(clockX - clockBorderWidth / 2, clockY - clockRadius - clockBorderWidth, clockBorderWidth, clockBorderWidth)
canvasContext.closePath()
canvasContext.beginPath()
// timer arc
canvasContext.arc(clockX, clockY, clockRadius - (clockBorderWidth + 3), degreesToRadians(clockTimerStartAngle), degreesToRadians(Math.ceil(clockTimerEndAngle)), true)
canvasContext.lineTo(clockX, clockY)
canvasContext.fill()
canvasContext.closePath()
canvasContext.save()
}
onCanvasLoaded(canvas: Canvas) {
}
onCanvasUnloaded() {
}
}
registerElement(componentName, () => require('./acr-dynamic-key.component').AcrDynamicKeyComponent)
the template:
<GridLayout id="container" width="100%" height="65" columns="100, *" (tap)="redraw()">
<StackLayout col="0">
<Canvas *ngIf="showCanvas" #canvas width="100%" height="100%" (ready)="onCanvasReady($event)"></Canvas>
</StackLayout>
<StackLayout col="1">
<Label id="title" class="text-white text-sm ml-1" [text]="localizePath.title | L"></Label>
<Label id="key" class="text-white text-lg font-bold tracking-wide" [text]="dyanamicKey"></Label>
</StackLayout>
</GridLayout>
Please try again using {N} 8.1
@triniwiz tested using NS 8.1 and canvas 1.0.0-debug.0 and the same problem: the canvas disappears while a navigation to another view is being performed on Android, I'm using Angular 12 with RootLayout as my wrapper for page-router-outlet btw, so the canvas becomes invisible when nsRouterLink is used, still needing the workaround that i have in previous comment.
You should stick with the alpha, debug is an actual debug version of the libs with the symbols included
Can you try with the latest , please reopen if the issue still persists
Home.vue.zip CanvasTest.vue.zip
In this simple test, Android graphics BufferQueue is lost after switching pages/tabs. It's not a full working demo but should be very easy to repro with what I've provided.
After timeout, orange draws over blue as expected (both platforms).
Page is unloaded and reloaded via tab navigation (BottomNavigation & TabContentItem)... iOS: Canvas appears the same, orange rectangle in blue. Android: Canvas has reverted to all blue (background color), and when 'fillRect' is called again I get errors...
06-23 15:42:01.878 15438 15438 I JS : 'draw canvas: Canvas
NS 6 + Vue tns version 8.0.2 Dependencies:
Dev Dependencies: