Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[2.0.0] support canvas on OpacityWIdget #39

Merged
merged 1 commit into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"flutterjs",
"lerp",
"linkedom",
"LTWH",
"meursyphus",
"popmotion",
"postbuild",
Expand Down
17 changes: 11 additions & 6 deletions packages/flitter-svelte/src/lib/Widget.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import { type Widget, Alignment, AppRunner, Container, Text } from '@meursyphus/flitter';
const browser = typeof window !== 'undefined';

export let renderer: 'svg' | 'canvas' = 'svg';
export let widget: Widget = Container({
width: Infinity,
height: Infinity,
Expand All @@ -22,7 +23,7 @@
export let width = '100%';
export let height = '300px';

let svgEl: SVGSVGElement;
let renderEl: SVGSVGElement | HTMLCanvasElement;
let containerEl: HTMLElement;
let runner: AppRunner;
let innerHTML: string = '';
Expand All @@ -40,12 +41,12 @@

onMount(() => {
runner = new AppRunner({
view: svgEl,
view: renderEl,
window: window,
document: document,
ssrSize: ssr?.size
});
svgEl.innerHTML = '';
renderEl.innerHTML = '';
runner.runApp(widget);
runner.onMount({
resizeTarget: containerEl
Expand All @@ -58,9 +59,13 @@
</script>

<div bind:this={containerEl} style="--width: {width}; --height: {height}">
<svg class="flitter" bind:this={svgEl}>
{@html innerHTML}
</svg>
{#if renderer === 'canvas' && browser}
<canvas class="flitter" bind:this={renderEl} />
{:else}
<svg class="flitter" bind:this={renderEl}>
{@html innerHTML}
</svg>
{/if}
</div>

<style>
Expand Down
26 changes: 23 additions & 3 deletions packages/flitter/src/component/base/BaseColoredBox.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { SvgPainter } from "../../framework";
import {
SvgPainter,
type SvgPaintContext,
type CanvasPaintingContext,
CanvasPainter,
} from "../../framework";
import SingleChildRenderObject from "../../renderobject/SingleChildRenderObject";
import type { SvgPaintContext } from "../../utils/type";
import SingleChildRenderObjectWidget from "../../widget/SingleChildRenderObjectWidget";
import type Widget from "../../widget/Widget";

Expand Down Expand Up @@ -43,9 +47,13 @@ class RenderColoredBox extends SingleChildRenderObject {
this._color = color;
}

createSvgPainter() {
override createSvgPainter() {
return new SvgPainterColoredBox(this);
}

override createCanvasPainter() {
return new CanvasPainterColoredBox(this);
}
}

class SvgPainterColoredBox extends SvgPainter {
Expand All @@ -69,4 +77,16 @@ class SvgPainterColoredBox extends SvgPainter {
}
}

class CanvasPainterColoredBox extends CanvasPainter {
get color() {
return (this.renderObject as RenderColoredBox).color;
}

protected override performPaint(paintContext: CanvasPaintingContext): void {
const { canvas: ctx } = paintContext;
ctx.fillStyle = this.color;
ctx.fillRect(0, 0, this.size.width, this.size.height);
}
}

export default ColoredBox;
2 changes: 1 addition & 1 deletion packages/flitter/src/component/base/BaseCustomPaint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { SvgPainter } from "../../framework";
import SingleChildRenderObject from "../../renderobject/SingleChildRenderObject";
import type { Constraints } from "../../type";
import { Size } from "../../type";
import type { SvgPaintContext } from "../../utils/type";
import type { SvgPaintContext } from "../../framework";
import SingleChildRenderObjectWidget from "../../widget/SingleChildRenderObjectWidget";
import type Widget from "../../widget/Widget";

Expand Down
2 changes: 1 addition & 1 deletion packages/flitter/src/component/base/BaseGestureDetector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { SvgPainter } from "../../framework";
import type { RenderObjectElement } from "../../element";
import SingleChildRenderObject from "../../renderobject/SingleChildRenderObject";
import { assert, createUniqueId } from "../../utils";
import type { SvgPaintContext } from "../../utils/type";
import type { SvgPaintContext } from "../../framework";
import SingleChildRenderObjectWidget from "../../widget/SingleChildRenderObjectWidget";
import type Widget from "../../widget/Widget";
import type { Offset } from "../../type";
Expand Down
21 changes: 19 additions & 2 deletions packages/flitter/src/component/base/BaseOpacity.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { SvgPainter } from "../../framework";
import {
SvgPainter,
CanvasPainter,
type CanvasPaintingContext,
} from "../../framework";
import SingleChildRenderObject from "../../renderobject/SingleChildRenderObject";
import { assert } from "../../utils";
import SingleChildRenderObjectWidget from "../../widget/SingleChildRenderObjectWidget";
Expand Down Expand Up @@ -52,9 +56,12 @@ class RenderOpacity extends SingleChildRenderObject {
this.size = this.child.size;
}
}
createSvgPainter(): SvgPainter {
override createSvgPainter(): SvgPainter {
return new SvgPainterOpacity(this);
}
override createCanvasPainter(): CanvasPainter {
return new CanvasPainterOpacity(this);
}
}

class SvgPainterOpacity extends SvgPainter {
Expand All @@ -67,4 +74,14 @@ class SvgPainterOpacity extends SvgPainter {
}
}

class CanvasPainterOpacity extends CanvasPainter {
get opacity() {
return (this.renderObject as RenderOpacity).opacityProp;
}

override performPaint(context: CanvasPaintingContext) {
context.canvas.globalAlpha *= this.opacity;
}
}

export default Opacity;
2 changes: 1 addition & 1 deletion packages/flitter/src/component/base/BaseRichText.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
TextOverflow,
Size,
} from "../../type";
import type { SvgPaintContext } from "../../utils/type";
import type { SvgPaintContext } from "../../framework";
import RenderObjectWidget from "../../widget/RenderObjectWidget";
import type InlineSpan from "../../type/_types/Inline-span";
import TextPainter from "../../type/_types/text-painter";
Expand Down
3 changes: 2 additions & 1 deletion packages/flitter/src/framework/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
export { default as BuildOwner } from "./BuildOwner";
export { RenderPipeline } from "./renderer/renderer";
export { RenderPipelineProvider } from "./renderer/provider";
export { SvgPainter } from "./renderer/svg/svg-painter";
export { SvgPainter, type SvgPaintContext } from "./renderer/svg/svg-painter";
export { CanvasPainter } from "./renderer/canvas/canvas-painter";
export { type CanvasPaintingContext } from "./renderer/canvas/canvas-painting-context";
export { default as Scheduler } from "./Scheduler";
export { default as RenderFrameDispatcher } from "./RenderFrameDispatcher";
export { default as GlobalKey } from "./Globalkey";
61 changes: 58 additions & 3 deletions packages/flitter/src/framework/renderer/canvas/canvas-painter.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,60 @@
import type { RenderObject } from "../../../renderobject/RenderObject";
import { Rect } from "../../../type";
import { NotImplementedError } from "../../../exception";
import { Painter } from "../renderer";
import type { CanvasRenderPipeline } from "./canvas-renderer";
import type { CanvasPaintingContext } from "./canvas-painting-context";
import { OffsetLayer, type ContainerLayer } from "./layer";
import { assert } from "src/utils";

export class CanvasPainter {
constructor(public readonly renderObject: RenderObject) {}
export class CanvasPainter extends Painter {
get renderOwner(): CanvasRenderPipeline {
return this.renderObject.renderOwner as CanvasRenderPipeline;
}
get isRepaintBoundary() {
return false;
}

paint(context: CanvasPaintingContext) {
this.needsPaint = false;
context.canvas.translate(this.offset.x, this.offset.y);
this.performPaint(context);
this.#paintChildren(context);
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
protected performPaint(context: CanvasPaintingContext) {}

#paintChildren(context: CanvasPaintingContext) {
this.renderObject.visitChildren(child => {
child.canvasPainter.paint(context);
});
}

get paintBounds(): Rect {
return Rect.fromLTWH({
left: this.offset.x,
top: this.offset.y,
width: this.size.width,
height: this.size.height,
});
}

#layer: ContainerLayer;
get layer() {
return this.#layer;
}
set layer(layer: ContainerLayer) {
this.#layer = layer;
}
updateCompositedLayer(oldLayer: ContainerLayer | null) {
assert(
this.isRepaintBoundary,
"updateCompositedLayer must be called on a repaint boundary",
);
return oldLayer ?? new OffsetLayer();
}

skippedPaintingOnLayer() {
throw new NotImplementedError("skippedPaintingOnLayer on CanvasPainter");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { assert } from "../../../utils";
import type { RenderObject } from "../../../renderobject";
import type { Rect } from "../../../type";
import {
type ContainerLayer,
PictureLayer,
PictureRecorder,
type Layer,
} from "./layer";

export class CanvasPaintingContext {
#estimateBound: Rect;
#containerLayer: ContainerLayer;
constructor(containerLayer: ContainerLayer, estimateBound: Rect) {
this.#containerLayer = containerLayer;
this.#estimateBound = estimateBound;
}
#currentLayer: PictureLayer | null;

static repaintCompositedChild(node: RenderObject): void {
assert(
node.canvasPainter.isRepaintBoundary,
"isRepaintBoundary must be true on repaintCompositedChild",
);
let childLayer = node.canvasPainter.layer;
if (childLayer == null) {
childLayer = node.canvasPainter.updateCompositedLayer(null);
node.canvasPainter.layer = childLayer;
} else {
const updatedChildLayer =
node.canvasPainter.updateCompositedLayer(childLayer);
assert(
childLayer === updatedChildLayer,
"updateCompositedLayer must return the same layer",
);
updatedChildLayer.removeAllChildren();
}

const childContext = new CanvasPaintingContext(
childLayer,
node.canvasPainter.paintBounds,
);
node.canvasPainter.paint(childContext);
childContext.stopRecording();
}

static updateLayerProperties(_: RenderObject): void {
console.warn("updateLayerProperties is not implemented");
}

#recorder: PictureRecorder;
#ctx: CanvasRenderingContext2D | null;
get canvas(): CanvasRenderingContext2D {
if (this.#ctx == null) {
this.#startRecording();
}
return this.#ctx;
}

#startRecording() {
this.#currentLayer = new PictureLayer(this.#estimateBound);
this.#recorder = new PictureRecorder(this.#estimateBound);
this.#ctx = this.#recorder.createCanvasContext();
this.#containerLayer.append(this.#currentLayer!);
}

stopRecording() {
this.#currentLayer.picture = this.#recorder.endRecording();
this.#recorder = null;
this.#ctx = null;
this.#currentLayer = null;
}

addLayer(layer: Layer) {
this.stopRecording();
this.#appendLayer(layer);
}

#appendLayer(layer: Layer) {
this.#containerLayer.append(layer);
}
}
Loading