update logo

improve font resolution logic
generate icons
improve timeline
This commit is contained in:
Enrico Bühler 2023-05-28 22:57:13 +02:00
parent 1baa3ae736
commit 28613c9214
38 changed files with 204 additions and 221 deletions

BIN
app/app-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 974 B

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 903 B

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 268 KiB

View File

@ -30,7 +30,7 @@ pub struct Paint {
pub struct TextPaint { pub struct TextPaint {
pub style: PaintStyle, pub style: PaintStyle,
pub align: TextAlign, pub align: TextAlign,
pub fontName: String, pub font_name: String,
pub size: f32, pub size: f32,
} }

View File

@ -1,6 +1,6 @@
use super::{ use super::{
entities::common::AnimationData, entities::common::AnimationData,
values::{AnimatedFloatVec2, AnimatedValue}, values::{AnimatedFloatVec2, AnimatedFloatVec3, AnimatedValue},
}; };
use crate::animation::timeline::Timeline; use crate::animation::timeline::Timeline;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -10,7 +10,7 @@ pub struct AnimatedTransform {
pub translate: AnimatedFloatVec2, pub translate: AnimatedFloatVec2,
pub scale: AnimatedFloatVec2, pub scale: AnimatedFloatVec2,
pub skew: AnimatedFloatVec2, pub skew: AnimatedFloatVec2,
pub rotate: AnimatedFloatVec2, pub rotate: AnimatedFloatVec3,
} }
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
@ -18,7 +18,7 @@ pub struct Transform {
pub translate: (f32, f32), pub translate: (f32, f32),
pub scale: (f32, f32), pub scale: (f32, f32),
pub skew: (f32, f32), pub skew: (f32, f32),
pub rotate: (f32, f32), pub rotate: (f32, f32, f32),
} }
impl AnimatedTransform { impl AnimatedTransform {

View File

@ -154,7 +154,7 @@ pub fn test_timeline_entities_at_frame(
color: Color::new(0, 0, 0, 1.0), color: Color::new(0, 0, 0, 1.0),
width: 10.0, width: 10.0,
}), }),
fontName: "Arial".to_string(), font_name: "Arial".to_string(),
align: TextAlign::Center, align: TextAlign::Center,
size: 20.0, size: 20.0,
}; };
@ -163,7 +163,7 @@ pub fn test_timeline_entities_at_frame(
style: PaintStyle::Fill(FillStyle { style: PaintStyle::Fill(FillStyle {
color: Color::new(0, 0, 0, 1.0), color: Color::new(0, 0, 0, 1.0),
}), }),
fontName: "Arial".to_string(), font_name: "Arial".to_string(),
align: TextAlign::Center, align: TextAlign::Center,
size: 10.0, size: 10.0,
}; };

View File

@ -1,16 +1,27 @@
use font_kit::source::SystemSource; use font_kit::source::SystemSource;
pub struct Font {
pub path: String,
}
#[tauri::command] #[tauri::command]
pub fn get_system_fonts() -> Option<Vec<String>> { pub fn get_system_fonts() -> Option<Vec<String>> {
let source = SystemSource::new(); let source = SystemSource::new();
let found_families = source.all_families(); let found_fonts = source.all_fonts();
found_families.ok() match found_fonts {
Ok(found_fonts) => {
let font_names: Vec<String> = found_fonts
.iter()
.map(|f| f.load())
.filter(|f| f.is_ok())
.map(|f| f.unwrap())
.map(|f| f.postscript_name())
.filter(|f| f.is_some())
.map(|f| f.unwrap())
.collect();
Some(font_names)
}
Err(_) => None,
}
} }
#[tauri::command] #[tauri::command]

View File

@ -102,10 +102,10 @@ export const TextProperties: FC<TextPropertiesProps> = ({
onUpdate({ onUpdate({
...entity, ...entity,
cache: { valid: false }, cache: { valid: false },
paint: { ...entity.paint, fontName: e.target.value }, paint: { ...entity.paint, font_name: e.target.value },
}) })
} }
value={entity.paint.fontName} value={entity.paint.font_name}
> >
{fonts.map((font) => ( {fonts.map((font) => (
<option value={font} key={font}> <option value={font} key={font}>
@ -173,11 +173,11 @@ export const StaggeredTextProperties: FC<StaggeredTextPropertiesProps> = ({
cache: { valid: false }, cache: { valid: false },
letter: { letter: {
...entity.letter, ...entity.letter,
paint: { ...entity.letter.paint, fontName: e.target.value }, paint: { ...entity.letter.paint, font_name: e.target.value },
}, },
}); });
}} }}
value={entity.letter.paint.fontName} value={entity.letter.paint.font_name}
> >
{fonts.map((font) => ( {fonts.map((font) => (
<option value={font} key={font}> <option value={font} key={font}>

View File

@ -0,0 +1,27 @@
import { ease } from "@unom/style";
import { motion } from "framer-motion";
import { AnimationData } from "primitives/AnimatedEntities";
import { Keyframe } from "primitives/Keyframe";
import { FC } from "react";
import { z } from "zod";
import { TIMELINE_SCALE } from "./common";
const KeyframeIndicator: FC<{
keyframe: z.input<typeof Keyframe>;
animationData: z.input<typeof AnimationData>;
}> = ({ keyframe, animationData }) => {
return (
<motion.div
animate={{
x: (animationData.offset + keyframe.offset) * TIMELINE_SCALE + 4,
}}
transition={ease.quint(0.4).out}
style={{
clipPath: "polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%)",
}}
className="bg-indigo-300 absolute w-2 h-2 z-30 top-[39%] select-none pointer-events-none"
/>
);
};
export default KeyframeIndicator;

View File

@ -1,7 +1,7 @@
import { FC } from "react"; import { FC } from "react";
import * as Slider from "@radix-ui/react-slider"; import * as Slider from "@radix-ui/react-slider";
import { useRenderStateStore } from "stores/render-state.store"; import { useRenderStateStore } from "stores/render-state.store";
import { TIMELINE_SCALE } from "./Timeline"; import { TIMELINE_SCALE } from "./common";
import { useTimelineStore } from "stores/timeline.store"; import { useTimelineStore } from "stores/timeline.store";
export type TimePickerProps = {}; export type TimePickerProps = {};
@ -12,19 +12,22 @@ const TimePicker: FC<TimePickerProps> = () => {
return ( return (
<Slider.Root <Slider.Root
className="relative flex select-none h-5 w-full items-center" className="relative flex items-center select-none touch-none h-5 shrink-0"
defaultValue={[50]} defaultValue={[50]}
style={{ width: TIMELINE_SCALE * timeline.duration }} style={{ width: TIMELINE_SCALE * 10 }}
value={[renderState.curr_frame]} value={[renderState.curr_frame]}
onValueChange={(val) => setCurrentFrame(val[0])} onValueChange={(val) => setCurrentFrame(val[0])}
max={timeline.fps * timeline.duration} max={timeline.fps * timeline.duration}
step={1} step={1}
aria-label="Current Frame" aria-label="Current Frame"
> >
<Slider.Track className="SliderTrack"> <Slider.Track className="bg-blackA10 relative grow rounded-full h-[3px]">
<Slider.Range className="SliderRange" /> <Slider.Range className="absolute bg-white rounded-full h-full" />
</Slider.Track> </Slider.Track>
<Slider.Thumb className="SliderThumb" /> <Slider.Thumb
className="block w-5 h-5 bg-white shadow-[0_2px_10px] shadow-blackA7 rounded-[10px] hover:bg-violet3 focus:outline-none focus:shadow-[0_0_0_5px] focus:shadow-blackA8"
aria-label="Volume"
/>
</Slider.Root> </Slider.Root>
); );
}; };

View File

@ -1,25 +1,13 @@
import { FC } from "react";
import { z } from "zod";
import { AnimatedEntity, AnimationData } from "primitives/AnimatedEntities";
import { Reorder, motion, useDragControls } from "framer-motion";
import TimePicker from "./TimePicker";
import { shallow } from "zustand/shallow";
import { useEntitiesStore } from "stores/entities.store";
import { ease } from "@unom/style"; import { ease } from "@unom/style";
import Timestamp from "./Timestamp"; import { useDragControls, Reorder, motion } from "framer-motion";
import { AnimationData, AnimatedEntity } from "primitives/AnimatedEntities";
import { Keyframe } from "primitives/Keyframe"; import { Keyframe } from "primitives/Keyframe";
import { flattenedKeyframesByEntity } from "utils"; import { FC } from "react";
import { PauseIcon, PlayIcon } from "@radix-ui/react-icons"; import { useEntitiesStore } from "stores/entities.store";
import { useRenderStateStore } from "stores/render-state.store"; import { z } from "zod";
import { shallow } from "zustand/shallow";
export type AnimationEntity = { import KeyframeIndicator from "./KeyframeIndicator";
offset: number; import { TIMELINE_SCALE, calculateOffset } from "./common";
duration: number;
};
type TimelineProps = {};
export const TIMELINE_SCALE = 50;
type TrackProps = { type TrackProps = {
animationData: z.input<typeof AnimationData>; animationData: z.input<typeof AnimationData>;
@ -29,24 +17,6 @@ type TrackProps = {
keyframes: Array<z.input<typeof Keyframe>>; keyframes: Array<z.input<typeof Keyframe>>;
}; };
const KeyframeIndicator: FC<{
keyframe: z.input<typeof Keyframe>;
animationData: z.input<typeof AnimationData>;
}> = ({ keyframe, animationData }) => {
return (
<motion.div
animate={{
x: (animationData.offset + keyframe.offset) * TIMELINE_SCALE + 4,
}}
transition={ease.quint(0.4).out}
style={{
clipPath: "polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%)",
}}
className="bg-indigo-300 absolute w-2 h-2 z-30 top-[39%] select-none pointer-events-none"
/>
);
};
const Track: FC<TrackProps> = ({ const Track: FC<TrackProps> = ({
keyframes, keyframes,
animationData, animationData,
@ -72,7 +42,7 @@ const Track: FC<TrackProps> = ({
value={entity} value={entity}
dragListener={false} dragListener={false}
dragControls={controls} dragControls={controls}
className="h-8 w-96 flex flex-1 flex-row gap-1 select-none" className="h-8 relative flex flex-1 flex-row gap-1 select-none"
> >
<div <div
onMouseDown={(e) => e.preventDefault()} onMouseDown={(e) => e.preventDefault()}
@ -94,8 +64,8 @@ const Track: FC<TrackProps> = ({
</div> </div>
<div <div
style={{ width: "1000px" }} style={{ width: TIMELINE_SCALE * 10 }}
className="flex h-full flex-row relative bg-gray-900 select-none" className="flex h-full flex-row relative bg-gray-900 select-none shrink-0"
> >
{keyframes.map((keyframe, index) => ( {keyframes.map((keyframe, index) => (
<KeyframeIndicator <KeyframeIndicator
@ -118,11 +88,11 @@ const Track: FC<TrackProps> = ({
onMouseDown={(e) => e.preventDefault()} onMouseDown={(e) => e.preventDefault()}
transition={ease.circ(0.6).out} transition={ease.circ(0.6).out}
dragElastic={false} dragElastic={false}
dragConstraints={{ left: 0, right: 900 }} dragConstraints={{ left: 0 }}
onDragEnd={(e, info) => { onDragEnd={(e, info) => {
let offset = info.offset.x; let offset = info.offset.x;
offset *= 0.01; offset = calculateOffset(offset);
const animationOffset = const animationOffset =
animationData.offset + offset < 0 animationData.offset + offset < 0
@ -156,11 +126,11 @@ const Track: FC<TrackProps> = ({
scale: 0.9, scale: 0.9,
}} }}
transition={ease.circ(0.6).out} transition={ease.circ(0.6).out}
dragConstraints={{ left: 0, right: 900 }} dragConstraints={{ left: 0 }}
onDragEnd={(e, info) => { onDragEnd={(e, info) => {
let offset = info.offset.x; let offset = info.offset.x;
offset *= 0.01; offset = calculateOffset(offset);
const duration = animationData.duration + offset; const duration = animationData.duration + offset;
@ -183,14 +153,16 @@ const Track: FC<TrackProps> = ({
whileTap={{ scaleY: 0.9 }} whileTap={{ scaleY: 0.9 }}
dragConstraints={{ dragConstraints={{
left: 0, left: 0,
right: 900,
}} }}
onMouseDown={(e) => e.preventDefault()} onMouseDown={(e) => e.preventDefault()}
transition={ease.circ(0.8).out} transition={ease.circ(0.8).out}
onDragEnd={(_e, info) => { onDragEnd={(_e, info) => {
let offset = info.offset.x; let offset = info.offset.x;
offset *= 0.01;
offset = calculateOffset(offset);
offset += animationData.offset; offset += animationData.offset;
updateEntity(index, { updateEntity(index, {
animation_data: { animation_data: {
...animationData, ...animationData,
@ -205,53 +177,4 @@ const Track: FC<TrackProps> = ({
); );
}; };
const Timeline: FC<TimelineProps> = () => { export default Track;
const { entities, setEntities } = useEntitiesStore((store) => ({
entities: store.entities,
setEntities: store.setEntities,
}));
const { setPlaying } = useRenderStateStore((store) => ({
setPlaying: store.setPlaying,
}));
return (
<div className="flex flex-col p-4 w-full border transition-colors focus-within:border-gray-400 border-gray-600 rounded-md">
<div className="flex flex-row">
<div className="flex flex-row">
<button onClick={() => setPlaying(true)} className="w-8 h-8">
<PlayIcon color="white" width="100%" height="100%" />
</button>
<button onClick={() => setPlaying(false)} className="w-8 h-8">
<PauseIcon color="white" width="100%" height="100%" />
</button>
</div>
<Timestamp />
</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 min-w-[200px]" />
<TimePicker />
</div>
<Reorder.Group
className="gap-1 flex-1 flex flex-col overflow-scroll"
values={entities}
onReorder={setEntities}
>
{entities.map((entity, index) => (
<Track
entity={entity}
key={entity.id}
name={entity.type}
index={index}
keyframes={flattenedKeyframesByEntity(entity)}
animationData={entity.animation_data}
/>
))}
</Reorder.Group>
</div>
</div>
);
};
export default Timeline;

View File

@ -0,0 +1,7 @@
export const TIMELINE_SCALE = 100;
export const calculateOffset = (offset: number) => {
let nextOffset = offset / TIMELINE_SCALE;
return nextOffset;
};

View File

@ -0,0 +1,67 @@
import { FC } from "react";
import { Reorder } from "framer-motion";
import TimePicker from "./Timepicker";
import { useEntitiesStore } from "stores/entities.store";
import Timestamp from "./Timestamp";
import { flattenedKeyframesByEntity } from "utils";
import { PauseIcon, PlayIcon } from "@radix-ui/react-icons";
import { useRenderStateStore } from "stores/render-state.store";
import Track from "./Track";
export type AnimationEntity = {
offset: number;
duration: number;
};
type TimelineProps = {};
const Timeline: FC<TimelineProps> = () => {
const { entities, setEntities } = useEntitiesStore((store) => ({
entities: store.entities,
setEntities: store.setEntities,
}));
const { setPlaying } = useRenderStateStore((store) => ({
setPlaying: store.setPlaying,
}));
return (
<div className="flex flex-col p-4 w-full border transition-colors focus-within:border-gray-400 border-gray-600 rounded-md">
<div className="flex flex-row">
<div className="flex flex-row">
<button onClick={() => setPlaying(true)} className="w-8 h-8">
<PlayIcon color="white" width="100%" height="100%" />
</button>
<button onClick={() => setPlaying(false)} className="w-8 h-8">
<PauseIcon color="white" width="100%" height="100%" />
</button>
</div>
<Timestamp />
</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 min-w-[200px]" />
<TimePicker />
</div>
<Reorder.Group
className="gap-1 flex-1 flex flex-col"
values={entities}
onReorder={setEntities}
>
{entities.map((entity, index) => (
<Track
entity={entity}
key={entity.id}
name={entity.type}
index={index}
keyframes={flattenedKeyframesByEntity(entity)}
animationData={entity.animation_data}
/>
))}
</Reorder.Group>
</div>
</div>
);
};
export default Timeline;

View File

@ -1,4 +1,3 @@
import { C } from "@tauri-apps/api/event-30ea0228";
import { BaseEntity } from "primitives/Entities"; import { BaseEntity } from "primitives/Entities";
import { z } from "zod"; import { z } from "zod";

View File

@ -99,7 +99,7 @@ export function calculateLetters(
dependencies: Dependencies dependencies: Dependencies
): StaggeredTextCache { ): StaggeredTextCache {
const fontData = dependencies.fonts.get( const fontData = dependencies.fonts.get(
entity.letter.paint.fontName entity.letter.paint.font_name
) as ArrayBuffer; ) as ArrayBuffer;
const typeface = CanvasKit.Typeface.MakeFreeTypeFaceFromData( const typeface = CanvasKit.Typeface.MakeFreeTypeFaceFromData(
@ -261,6 +261,12 @@ export default function drawStaggeredText(
canvas.scale(letterTransform.scale[0], letterTransform.scale[1]); canvas.scale(letterTransform.scale[0], letterTransform.scale[1]);
canvas.rotate(
letterTransform.rotate[0],
letterTransform.rotate[1],
letterTransform.rotate[2]
);
canvas.translate( canvas.translate(
-origin[0] + measuredLetter.offset.x, -origin[0] + measuredLetter.offset.x,
-origin[1] + lineOffset -origin[1] + lineOffset

View File

@ -17,7 +17,9 @@ export function buildTextCache(
entity: z.output<typeof TextEntity>, entity: z.output<typeof TextEntity>,
dependencies: Dependencies dependencies: Dependencies
): TextCache { ): TextCache {
const fontData = dependencies.fonts.get(entity.paint.fontName) as ArrayBuffer; const fontData = dependencies.fonts.get(
entity.paint.font_name
) as ArrayBuffer;
const fontManager = CanvasKit.FontMgr.FromData(fontData) as FontMgr; const fontManager = CanvasKit.FontMgr.FromData(fontData) as FontMgr;
@ -43,7 +45,7 @@ export default function drawText(
const pStyle = new CanvasKit.ParagraphStyle({ const pStyle = new CanvasKit.ParagraphStyle({
textStyle: { textStyle: {
color: color, color: color,
fontFamilies: [entity.paint.fontName], fontFamilies: [entity.paint.font_name],
fontSize: entity.paint.size, fontSize: entity.paint.size,
}, },
textDirection: CanvasKit.TextDirection.LTR, textDirection: CanvasKit.TextDirection.LTR,

View File

@ -1,7 +1,11 @@
import { AnimatedEntity } from "primitives/AnimatedEntities"; import { AnimatedEntity } from "primitives/AnimatedEntities";
import { Color } from "primitives/Paint"; import { Color } from "primitives/Paint";
import { Timeline } from "primitives/Timeline"; import { Timeline } from "primitives/Timeline";
import { staticAnimatedNumber, staticAnimatedVec2 } from "primitives/Values"; import {
staticAnimatedNumber,
staticAnimatedVec2,
staticAnimatedVec3,
} from "primitives/Values";
import { z } from "zod"; import { z } from "zod";
import { v4 as uuid } from "uuid"; import { v4 as uuid } from "uuid";
@ -138,7 +142,7 @@ function buildText(
type: "Fill", type: "Fill",
color, color,
}, },
fontName: "Arial", font_name: "Arial",
size, size,
align: "Center", align: "Center",
}, },
@ -203,7 +207,7 @@ function buildStaggeredText(
stagger: 0.05, stagger: 0.05,
letter: { letter: {
paint: { paint: {
fontName: "Arial", font_name: "Arial",
style: { style: {
type: "Fill", type: "Fill",
color, color,
@ -213,7 +217,7 @@ function buildStaggeredText(
}, },
transform: { transform: {
translate: staticAnimatedVec2(0, 0), translate: staticAnimatedVec2(0, 0),
rotate: staticAnimatedVec2(0, 0), rotate: staticAnimatedVec3(0, 0, 45),
skew: staticAnimatedVec2(0, 0), skew: staticAnimatedVec2(0, 0),
scale: { scale: {
keyframes: [ keyframes: [
@ -227,7 +231,7 @@ function buildStaggeredText(
mass: 1, mass: 1,
damping: 15, damping: 15,
}, },
value: 0.0, value: 5.0,
offset: 0.0, offset: 0.0,
}, },
{ {
@ -246,11 +250,11 @@ function buildStaggeredText(
{ {
interpolation: { interpolation: {
type: "Spring", type: "Spring",
stiffness: 200, stiffness: 300,
mass: 1, mass: 1,
damping: 15, damping: 15,
}, },
value: 0.0, value: -10.0,
offset: 0.0, offset: 0.0,
}, },
{ {

View File

@ -6,7 +6,7 @@ import {
RectEntity, RectEntity,
TextEntity, TextEntity,
} from "./Entities"; } from "./Entities";
import { AnimatedVec2 } from "./Values"; import { AnimatedVec2, AnimatedVec3 } from "./Values";
import { TextPaint } from "./Paint"; import { TextPaint } from "./Paint";
export const AnimationData = z.object({ export const AnimationData = z.object({
@ -21,7 +21,7 @@ export const AnimatedTransform = z.object({
/** Skews by the given animated vec2 */ /** Skews by the given animated vec2 */
skew: AnimatedVec2, skew: AnimatedVec2,
/** Rotates by the given animated vec2 */ /** Rotates by the given animated vec2 */
rotate: AnimatedVec2, rotate: AnimatedVec3,
/** Scales on the x and y axis by the given animated vec2 */ /** Scales on the x and y axis by the given animated vec2 */
scale: AnimatedVec2, scale: AnimatedVec2,
}); });

View File

@ -1,5 +1,5 @@
import { z } from "zod"; import { z } from "zod";
import { Vec2 } from "./Values"; import { Vec2, Vec3 } from "./Values";
import { Paint, TextPaint } from "./Paint"; import { Paint, TextPaint } from "./Paint";
const EntityTypeOptions = ["Text", "Ellipse", "Rect", "StaggeredText"] as const; const EntityTypeOptions = ["Text", "Ellipse", "Rect", "StaggeredText"] as const;
@ -8,7 +8,7 @@ export const EntityType = z.enum(EntityTypeOptions);
export const Transform = z.object({ export const Transform = z.object({
skew: Vec2, skew: Vec2,
rotate: Vec2, rotate: Vec3,
translate: Vec2, translate: Vec2,
scale: Vec2, scale: Vec2,
}); });

View File

@ -35,7 +35,7 @@ export const Paint = z.object({
export const TextPaint = z.object({ export const TextPaint = z.object({
style: PaintStyle, style: PaintStyle,
align: TextAlign, align: TextAlign,
fontName: z.string(), font_name: z.string(),
size: z.number().min(0), size: z.number().min(0),
}); });

View File

@ -3,6 +3,7 @@ import { Keyframes } from "./Keyframe";
import { Interpolation } from "./Interpolation"; import { Interpolation } from "./Interpolation";
export const Vec2 = z.array(z.number()).length(2); export const Vec2 = z.array(z.number()).length(2);
export const Vec3 = z.array(z.number()).length(3);
export const AnimatedNumber = z.object({ export const AnimatedNumber = z.object({
keyframes: Keyframes, keyframes: Keyframes,
@ -12,6 +13,10 @@ export const AnimatedVec2 = z.object({
keyframes: z.array(AnimatedNumber).length(2), keyframes: z.array(AnimatedNumber).length(2),
}); });
export const AnimatedVec3 = z.object({
keyframes: z.array(AnimatedNumber).length(3),
});
export function staticAnimatedNumber( export function staticAnimatedNumber(
number: number number: number
): z.infer<typeof AnimatedNumber> { ): z.infer<typeof AnimatedNumber> {
@ -33,35 +38,22 @@ export function staticAnimatedNumber(
export function staticAnimatedVec2( export function staticAnimatedVec2(
x: number, x: number,
y: number y: number
): z.infer<typeof AnimatedVec2> {
return {
keyframes: [staticAnimatedNumber(x), staticAnimatedNumber(y)],
};
}
export function staticAnimatedVec3(
x: number,
y: number,
z: number
): z.infer<typeof AnimatedVec2> { ): z.infer<typeof AnimatedVec2> {
return { return {
keyframes: [ keyframes: [
{ staticAnimatedNumber(x),
keyframes: { staticAnimatedNumber(y),
values: [ staticAnimatedNumber(z),
{
interpolation: {
type: "Linear",
},
value: x,
offset: 0,
},
],
},
},
{
keyframes: {
values: [
{
interpolation: {
type: "Linear",
},
value: y,
offset: 0,
},
],
},
},
], ],
}; };
} }

View File

@ -33,10 +33,10 @@ export class DependenciesService {
entities.forEach((entity) => { entities.forEach((entity) => {
switch (entity.type) { switch (entity.type) {
case EntityType.Enum.Text: case EntityType.Enum.Text:
fontNames.add(entity.paint.fontName); fontNames.add(entity.paint.font_name);
break; break;
case EntityType.Enum.StaggeredText: case EntityType.Enum.StaggeredText:
fontNames.add(entity.letter.paint.fontName); fontNames.add(entity.letter.paint.font_name);
break; break;
default: default:
break; break;

View File

@ -74,61 +74,3 @@ body,
height: 100%; height: 100%;
overflow: hidden; overflow: hidden;
} }
.SliderRoot {
position: relative;
display: flex;
align-items: center;
user-select: none;
touch-action: none;
width: 200px;
height: 20px;
}
.SliderTrack {
background-color: var(--black);
position: relative;
flex-grow: 1;
border-radius: 9999px;
height: 3px;
}
.SliderRange {
position: absolute;
background-color: #ddd;
border-radius: 9999px;
height: 100%;
}
.SliderThumb {
position: relative;
z-index: 100;
transition: opacity 0.1s linear, filter 0.1s linear;
}
.SliderThumb::before {
content: "";
background-color: var(--indigo-400);
width: 20px;
height: 20px;
position: absolute;
left: -8.5px;
top: -10px;
clip-path: polygon(100% 0, 0 0, 50% 75%);
display: block;
@apply bg-indigo-300;
}
.SliderThumb::after {
content: "";
position: absolute;
top: 0;
width: 3px;
height: 1000px;
z-index: 200;
opacity: 1;
@apply bg-indigo-300;
}