fix cache
add font propertie to rust text paint layout improvements
This commit is contained in:
parent
b671f9ee47
commit
1baa3ae736
Binary file not shown.
@ -30,6 +30,7 @@ pub struct Paint {
|
||||
pub struct TextPaint {
|
||||
pub style: PaintStyle,
|
||||
pub align: TextAlign,
|
||||
pub fontName: String,
|
||||
pub size: f32,
|
||||
}
|
||||
|
||||
|
@ -154,6 +154,7 @@ pub fn test_timeline_entities_at_frame(
|
||||
color: Color::new(0, 0, 0, 1.0),
|
||||
width: 10.0,
|
||||
}),
|
||||
fontName: "Arial".to_string(),
|
||||
align: TextAlign::Center,
|
||||
size: 20.0,
|
||||
};
|
||||
@ -162,6 +163,7 @@ pub fn test_timeline_entities_at_frame(
|
||||
style: PaintStyle::Fill(FillStyle {
|
||||
color: Color::new(0, 0, 0, 1.0),
|
||||
}),
|
||||
fontName: "Arial".to_string(),
|
||||
align: TextAlign::Center,
|
||||
size: 10.0,
|
||||
};
|
||||
|
@ -20,12 +20,12 @@ export default function App() {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="bg-gray-950 h-full w-full flex flex-col">
|
||||
<div className="bg-gray-950 overflow-y-hidden h-full w-full flex flex-col">
|
||||
<MenuBar />
|
||||
<div className="flex flex-row w-full h-full">
|
||||
<ToolBar />
|
||||
<div className="flex flex-col ml-4 w-full h-full">
|
||||
<div className="flex gap-4 flex-row mb-4 justify-center items-center">
|
||||
<div className="flex flex-col ml-4 mr-4 mt-4 w-full h-full overflow-x-hidden">
|
||||
<div className="flex gap-4 flex-col lg:flex-row mb-4 justify-center items-center">
|
||||
<Canvas />
|
||||
<PropertiesContainer>
|
||||
<Properties />
|
||||
|
@ -11,6 +11,7 @@ import { FC } from "react";
|
||||
import { z } from "zod";
|
||||
import { AnimatedVec2Properties, ColorProperties } from "./Values";
|
||||
import { PropertiesProps } from "./common";
|
||||
import { useFontsStore } from "stores/fonts.store";
|
||||
|
||||
type TextPropertiesProps = PropertiesProps<z.input<typeof AnimatedTextEntity>>;
|
||||
type StaggeredTextPropertiesProps = PropertiesProps<
|
||||
@ -66,6 +67,8 @@ export const TextProperties: FC<TextPropertiesProps> = ({
|
||||
entity,
|
||||
onUpdate,
|
||||
}) => {
|
||||
const { fonts } = useFontsStore();
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
variants={{ enter: { opacity: 1, y: 0 }, from: { opacity: 0, y: 50 } }}
|
||||
@ -92,6 +95,38 @@ export const TextProperties: FC<TextPropertiesProps> = ({
|
||||
}
|
||||
></input>
|
||||
</label>
|
||||
<label className="flex flex-col items-start">
|
||||
<span className="label">Font</span>
|
||||
<select
|
||||
onChange={(e) =>
|
||||
onUpdate({
|
||||
...entity,
|
||||
cache: { valid: false },
|
||||
paint: { ...entity.paint, fontName: e.target.value },
|
||||
})
|
||||
}
|
||||
value={entity.paint.fontName}
|
||||
>
|
||||
{fonts.map((font) => (
|
||||
<option value={font} key={font}>
|
||||
{font}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span className="label">Size</span>
|
||||
<input
|
||||
value={entity.paint.size}
|
||||
onChange={(e) =>
|
||||
onUpdate({
|
||||
...entity,
|
||||
cache: { valid: false },
|
||||
paint: { ...entity.paint, size: Number(e.target.value) },
|
||||
})
|
||||
}
|
||||
></input>
|
||||
</label>
|
||||
<AnimatedVec2Properties
|
||||
onUpdate={(updatedEntity) =>
|
||||
onUpdate({ ...entity, origin: updatedEntity })
|
||||
@ -107,6 +142,8 @@ export const StaggeredTextProperties: FC<StaggeredTextPropertiesProps> = ({
|
||||
entity,
|
||||
onUpdate,
|
||||
}) => {
|
||||
const { fonts } = useFontsStore();
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
variants={{ enter: { opacity: 1, y: 0 }, from: { opacity: 0, y: 50 } }}
|
||||
@ -127,6 +164,28 @@ export const StaggeredTextProperties: FC<StaggeredTextPropertiesProps> = ({
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<label className="flex flex-col items-start">
|
||||
<span className="label">Font</span>
|
||||
<select
|
||||
onChange={(e) => {
|
||||
onUpdate({
|
||||
...entity,
|
||||
cache: { valid: false },
|
||||
letter: {
|
||||
...entity.letter,
|
||||
paint: { ...entity.letter.paint, fontName: e.target.value },
|
||||
},
|
||||
});
|
||||
}}
|
||||
value={entity.letter.paint.fontName}
|
||||
>
|
||||
{fonts.map((font) => (
|
||||
<option value={font} key={font}>
|
||||
{font}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label className="flex flex-col items-start">
|
||||
<span className="label">Size</span>
|
||||
<input
|
||||
@ -134,6 +193,7 @@ export const StaggeredTextProperties: FC<StaggeredTextPropertiesProps> = ({
|
||||
onChange={(e) =>
|
||||
onUpdate({
|
||||
...entity,
|
||||
cache: { valid: false },
|
||||
letter: {
|
||||
...entity.letter,
|
||||
paint: {
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
|
||||
const PropertiesContainer: FC<{ children: ReactNode }> = ({ children }) => {
|
||||
return (
|
||||
<div className="w-full rounded-md h-[500px] overflow-auto border transition-colors focus-within:border-gray-400 border-gray-600 flex flex-col items-start p-4">
|
||||
<div className="w-full rounded-md lg:h-[500px] overflow-auto border transition-colors focus-within:border-gray-400 border-gray-600 flex flex-col items-start p-4">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
@ -72,12 +72,12 @@ const Track: FC<TrackProps> = ({
|
||||
value={entity}
|
||||
dragListener={false}
|
||||
dragControls={controls}
|
||||
className="h-8 w-full flex flex-row gap-1 select-none"
|
||||
className="h-8 w-96 flex flex-1 flex-row gap-1 select-none"
|
||||
>
|
||||
<div
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
onPointerDown={(e) => controls.start(e)}
|
||||
className={`h-full transition-all rounded-sm flex-shrink-0 w-96 p-1 px-2 flex flex-row ${
|
||||
className={`h-full transition-all rounded-sm min-w-[200px] p-1 px-2 flex flex-row ${
|
||||
selectedEntity === index ? "bg-gray-800" : "bg-gray-900"
|
||||
}`}
|
||||
>
|
||||
@ -87,7 +87,7 @@ const Track: FC<TrackProps> = ({
|
||||
? deselectEntity()
|
||||
: selectEntity(index)
|
||||
}
|
||||
className="text-white-800 select-none pointer-events-none"
|
||||
className="text-white-800 select-none cursor-pointer"
|
||||
>
|
||||
{name}
|
||||
</h3>
|
||||
@ -230,11 +230,11 @@ const Timeline: FC<TimelineProps> = () => {
|
||||
</div>
|
||||
<div className="gap-1 flex flex-col overflow-y-hidden">
|
||||
<div className="z-20 flex flex-row gap-2">
|
||||
<div className="flex-shrink-0 w-96" />
|
||||
<div className="flex-shrink-0 min-w-[200px]" />
|
||||
<TimePicker />
|
||||
</div>
|
||||
<Reorder.Group
|
||||
className="gap-1 flex flex-col"
|
||||
className="gap-1 flex-1 flex flex-col overflow-scroll"
|
||||
values={entities}
|
||||
onReorder={setEntities}
|
||||
>
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { C } from "@tauri-apps/api/event-30ea0228";
|
||||
import { BaseEntity } from "primitives/Entities";
|
||||
import { z } from "zod";
|
||||
|
||||
@ -20,7 +21,12 @@ export function handleEntityCache<
|
||||
if (cached) {
|
||||
cache.cleanup(cached);
|
||||
}
|
||||
return cache.build();
|
||||
|
||||
const nextCache = cache.build();
|
||||
|
||||
cache.set(entity.id, nextCache);
|
||||
|
||||
return nextCache;
|
||||
} else {
|
||||
if (!cached) {
|
||||
const nextCache = cache.build();
|
||||
|
@ -124,7 +124,7 @@ export class Drawer {
|
||||
animatedEntities: z.input<typeof AnimatedEntities>,
|
||||
prepareDependencies: boolean
|
||||
) {
|
||||
console.time("calculate");
|
||||
// console.time("calculate");
|
||||
|
||||
if (this.didLoad) {
|
||||
const renderState = useRenderStateStore.getState().renderState;
|
||||
@ -145,20 +145,20 @@ export class Drawer {
|
||||
}
|
||||
);
|
||||
} else {
|
||||
console.timeEnd("calculate");
|
||||
// console.timeEnd("calculate");
|
||||
}
|
||||
}
|
||||
|
||||
requestRedraw(rebuild: boolean) {
|
||||
if (this.didLoad && this.surface) {
|
||||
if (this.didLoad && this.surface && !this.isLocked) {
|
||||
if (rebuild && this.raf !== undefined) {
|
||||
cancelAnimationFrame(this.raf);
|
||||
this.surface.flush();
|
||||
// this.surface.flush();
|
||||
this.raf = this.surface.requestAnimationFrame((canvas) =>
|
||||
this.draw(canvas)
|
||||
);
|
||||
} else {
|
||||
this.surface.flush();
|
||||
// this.surface.flush();
|
||||
this.raf = this.surface.requestAnimationFrame((canvas) =>
|
||||
this.draw(canvas)
|
||||
);
|
||||
@ -169,7 +169,7 @@ export class Drawer {
|
||||
draw(canvas: Canvas) {
|
||||
if (this.CanvasKit && this.entities && !this.isLocked) {
|
||||
this.isLocked = true;
|
||||
console.time("draw");
|
||||
//console.time("draw");
|
||||
const CanvasKit = this.CanvasKit;
|
||||
|
||||
canvas.clear(CanvasKit.WHITE);
|
||||
@ -191,12 +191,19 @@ export class Drawer {
|
||||
TextCache,
|
||||
TextEntityCache
|
||||
>(entity, {
|
||||
build: () =>
|
||||
buildTextCache(
|
||||
build: () => {
|
||||
const cache = buildTextCache(
|
||||
CanvasKit,
|
||||
entity,
|
||||
this.dependenciesService.dependencies
|
||||
),
|
||||
);
|
||||
|
||||
useEntitiesStore
|
||||
.getState()
|
||||
.updateEntityById(entity.id, { cache: { valid: true } });
|
||||
|
||||
return cache;
|
||||
},
|
||||
get: () => this.cache.text.get(entity.id),
|
||||
set: (id, cache) => this.cache.text.set(id, cache),
|
||||
cleanup: (cache) => {
|
||||
@ -245,7 +252,7 @@ export class Drawer {
|
||||
}
|
||||
});
|
||||
this.isLocked = false;
|
||||
console.timeEnd("draw");
|
||||
//console.timeEnd("draw");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -203,82 +203,86 @@ export default function drawStaggeredText(
|
||||
|
||||
buildPaintStyle(CanvasKit, paint, entity.letter.paint);
|
||||
|
||||
// Draw all those runs.
|
||||
for (let i = 0; i < measuredLetters.length; i++) {
|
||||
const measuredLetter = measuredLetters[i];
|
||||
if (glyphs) {
|
||||
// Draw all those runs.
|
||||
for (let i = 0; i < measuredLetters.length; i++) {
|
||||
const measuredLetter = measuredLetters[i];
|
||||
|
||||
const glyph = glyphs.subarray(i, i + 1);
|
||||
const glyph = glyphs.subarray(i, i + 1);
|
||||
|
||||
const blob = CanvasKit.TextBlob.MakeFromGlyphs(
|
||||
glyph as unknown as Array<number>,
|
||||
font
|
||||
);
|
||||
if (blob) {
|
||||
canvas.save();
|
||||
const blob = CanvasKit.TextBlob.MakeFromGlyphs(
|
||||
glyph as unknown as Array<number>,
|
||||
font
|
||||
);
|
||||
if (blob) {
|
||||
canvas.save();
|
||||
|
||||
const width = measuredLetters
|
||||
.filter((letter) => letter.line === 0)
|
||||
.reduce((prev, curr) => curr.bounds.x_advance + prev, 0);
|
||||
const width = measuredLetters
|
||||
.filter((letter) => letter.line === 0)
|
||||
.reduce((prev, curr) => curr.bounds.x_advance + prev, 0);
|
||||
|
||||
const lineOffset = (entity.letter.paint.size / 2) * measuredLetter.line;
|
||||
const lineOffset = (entity.letter.paint.size / 2) * measuredLetter.line;
|
||||
|
||||
const entityOrigin = [
|
||||
entity.origin[0] - width / 2,
|
||||
entity.origin[1] + lineOffset,
|
||||
];
|
||||
const entityOrigin = [
|
||||
entity.origin[0] - width / 2,
|
||||
entity.origin[1] + lineOffset,
|
||||
];
|
||||
|
||||
const lineCount = measuredLetters
|
||||
.map((e) => e.line)
|
||||
.sort((a, b) => a - b)[measuredLetters.length - 1];
|
||||
const lineCount = measuredLetters
|
||||
.map((e) => e.line)
|
||||
.sort((a, b) => a - b)[measuredLetters.length - 1];
|
||||
|
||||
if (entity.letter.transform && entity.letter.transform[i]) {
|
||||
const letterTransform = entity.letter.transform[i];
|
||||
const letterOrigin = [0, 0];
|
||||
if (entity.letter.transform && entity.letter.transform[i]) {
|
||||
const letterTransform = entity.letter.transform[i];
|
||||
const letterOrigin = [0, 0];
|
||||
|
||||
let origin = letterOrigin.map(
|
||||
(val, index) => val + entityOrigin[index]
|
||||
);
|
||||
let origin = letterOrigin.map(
|
||||
(val, index) => val + entityOrigin[index]
|
||||
);
|
||||
|
||||
// Calculate the spacing
|
||||
// Calculate the spacing
|
||||
|
||||
const spacing =
|
||||
measuredLetter.bounds.x_advance - measuredLetter.bounds.width;
|
||||
const spacing =
|
||||
measuredLetter.bounds.x_advance - measuredLetter.bounds.width;
|
||||
|
||||
//console.log(spacing);
|
||||
//console.log(spacing);
|
||||
|
||||
// Center the origin
|
||||
// Center the origin
|
||||
|
||||
origin[0] =
|
||||
origin[0] + measuredLetter.bounds.width / 2 + measuredLetter.offset.x;
|
||||
origin[1] = origin[1] - metrics.descent + lineOffset;
|
||||
origin[0] =
|
||||
origin[0] +
|
||||
measuredLetter.bounds.width / 2 +
|
||||
measuredLetter.offset.x;
|
||||
origin[1] = origin[1] - metrics.descent + lineOffset;
|
||||
|
||||
//console.log(measuredLetter.bounds);
|
||||
//console.log(measuredLetter.bounds);
|
||||
|
||||
canvas.translate(origin[0], origin[1]);
|
||||
canvas.translate(origin[0], origin[1]);
|
||||
|
||||
canvas.scale(letterTransform.scale[0], letterTransform.scale[1]);
|
||||
canvas.scale(letterTransform.scale[0], letterTransform.scale[1]);
|
||||
|
||||
canvas.translate(
|
||||
-origin[0] + measuredLetter.offset.x,
|
||||
-origin[1] + lineOffset
|
||||
);
|
||||
canvas.translate(
|
||||
-origin[0] + measuredLetter.offset.x,
|
||||
-origin[1] + lineOffset
|
||||
);
|
||||
|
||||
/* canvas.translate(
|
||||
/* canvas.translate(
|
||||
measuredLetter.offset.x + measuredLetter.bounds.width / 2,
|
||||
0
|
||||
); */
|
||||
}
|
||||
}
|
||||
|
||||
/* canvas.translate(
|
||||
/* canvas.translate(
|
||||
width * -0.5,
|
||||
lineCount * (-entity.letter.paint.size / 2)
|
||||
); */
|
||||
|
||||
canvas.drawTextBlob(blob, entityOrigin[0], entityOrigin[1], paint);
|
||||
canvas.drawTextBlob(blob, entityOrigin[0], entityOrigin[1], paint);
|
||||
|
||||
canvas.restore();
|
||||
canvas.restore();
|
||||
|
||||
blob.delete();
|
||||
blob.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -138,6 +138,7 @@ function buildText(
|
||||
type: "Fill",
|
||||
color,
|
||||
},
|
||||
fontName: "Arial",
|
||||
size,
|
||||
align: "Center",
|
||||
},
|
||||
@ -202,6 +203,7 @@ function buildStaggeredText(
|
||||
stagger: 0.05,
|
||||
letter: {
|
||||
paint: {
|
||||
fontName: "Arial",
|
||||
style: {
|
||||
type: "Fill",
|
||||
color,
|
||||
|
@ -35,7 +35,7 @@ export const Paint = z.object({
|
||||
export const TextPaint = z.object({
|
||||
style: PaintStyle,
|
||||
align: TextAlign,
|
||||
fontName: z.string().default("Helvetica-Bold"),
|
||||
fontName: z.string(),
|
||||
size: z.number().min(0),
|
||||
});
|
||||
|
||||
|
@ -28,10 +28,9 @@ export class DependenciesService {
|
||||
) {
|
||||
const fontNames = new Set<string>();
|
||||
|
||||
entities.forEach((entity) => {
|
||||
if (entity.type === EntityType.Enum.Text) {
|
||||
}
|
||||
console.log(entities);
|
||||
|
||||
entities.forEach((entity) => {
|
||||
switch (entity.type) {
|
||||
case EntityType.Enum.Text:
|
||||
fontNames.add(entity.paint.fontName);
|
||||
@ -62,22 +61,26 @@ export class DependenciesService {
|
||||
async loadFonts(fontNames: Set<string>) {
|
||||
const resolveFonts: Array<Promise<void>> = [];
|
||||
|
||||
const loadFont = async (fontName: string) => {
|
||||
return invoke("get_system_font", { fontName }).then((data) => {
|
||||
if (Array.isArray(data)) {
|
||||
const u8 = new Uint8Array(data as any);
|
||||
const buffer = typedArrayToBuffer(u8);
|
||||
this.dependencies.fonts.set(fontName, buffer);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
fontNames.forEach((fontName) => {
|
||||
if (!this.dependencies.fonts.has(fontName)) {
|
||||
resolveFonts.push(
|
||||
invoke("get_system_font", { fontName }).then((data) => {
|
||||
if (Array.isArray(data)) {
|
||||
const u8 = new Uint8Array(data as any);
|
||||
const buffer = typedArrayToBuffer(u8);
|
||||
this.dependencies.fonts.set(fontName, buffer);
|
||||
}
|
||||
})
|
||||
);
|
||||
resolveFonts.push(loadFont(fontName));
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(resolveFonts);
|
||||
|
||||
console.log(this.dependencies);
|
||||
console.log(fontNames);
|
||||
|
||||
// console.log(this.dependencies);
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,12 @@ export class PlaybackService {
|
||||
}
|
||||
});
|
||||
|
||||
useEntitiesStore.subscribe((state) => {
|
||||
if (!this.playing) {
|
||||
this.seek();
|
||||
}
|
||||
});
|
||||
|
||||
this.seek();
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@ interface FontsStore {
|
||||
|
||||
const useFontsStore = create<FontsStore>((set) => ({
|
||||
fonts: [],
|
||||
setFonts: (fonts) => ({ fonts }),
|
||||
setFonts: (fonts) => set({ fonts }),
|
||||
}));
|
||||
|
||||
export { useFontsStore };
|
||||
|
@ -7,7 +7,7 @@ interface TimelineStore {
|
||||
}
|
||||
|
||||
const useTimelineStore = create<TimelineStore>((set) => ({
|
||||
fps: 120,
|
||||
fps: 30,
|
||||
size: [1280, 720],
|
||||
duration: 10.0,
|
||||
}));
|
||||
|
@ -72,6 +72,7 @@ body,
|
||||
#root {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.SliderRoot {
|
||||
|
Loading…
x
Reference in New Issue
Block a user