Skip to content

Commit f48491c

Browse files
committed
feat:
- Group: template effects (moveTo, scaleTo, rotateTo, alignTo, fadeIn, fadeOut, moveBy, scaleBy, rotateBy) now broadcast to children via _broadcast instead of using the group's phantom meta as animation source - moveTo, scaleTo, alignTo snapshot src at call time to prevent mutation during animation loop - Offset now syncs self.meta.position with _basePosition so chained moveBy calls read the right source - apply() overloaded with singledispatch to accept (IShader, start, duration) tuples — used by click() - click(): fixed frame index passed as seconds (unit mismatch causing laggy animation) and phase overlap (i resets to 0 per phase) - inputCreation and trackProps typed with ParamSpec + TypeVar so decorated __init__ signatures are preserved in the IDE
1 parent 37d12c2 commit f48491c

38 files changed

Lines changed: 750 additions & 317 deletions

include/shader/IVertexShader.hpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ enum VertexShader {
3131
Align,
3232
Position,
3333
Scale,
34-
Rotate,
34+
Rotation,
3535
Opacity,
3636
Hide,
3737
Show,
@@ -46,7 +46,7 @@ const std::map<std::string, VertexShader> getTransformFromString = {
4646
{"Position", VertexShader::Position},
4747
{"Scale", VertexShader::Scale},
4848
{"Align", VertexShader::Align},
49-
{"Rotate", VertexShader::Rotate},
49+
{"Rotation", VertexShader::Rotation},
5050
{"Opacity", VertexShader::Opacity},
5151
{"Hide", VertexShader::Hide},
5252
{"Show", VertexShader::Show},
@@ -125,7 +125,7 @@ inline void getMetadataFromArgs(VertexShader t, const json::object_t& args, Meta
125125
meta.scale.y = args.at("y");
126126
break;
127127
}
128-
case Rotate: {
128+
case Rotation: {
129129
meta.rotation = args.at("degree");
130130
break;
131131
}

include/utils/Logger.hpp

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
** EPITECH PROJECT, 2026
3+
** video-code
4+
** File description:
5+
** Logger
6+
*/
7+
8+
#pragma once
9+
10+
#include <format>
11+
#include <iostream>
12+
#include <nlohmann/json.hpp>
13+
#include <string>
14+
15+
namespace VC
16+
{
17+
namespace Color
18+
{
19+
constexpr const char* CYAN = "\033[36m";
20+
constexpr const char* RED = "\033[31m";
21+
constexpr const char* GREEN = "\033[32m";
22+
constexpr const char* MAGENTA = "\033[35m";
23+
constexpr const char* RESET = "\033[0m";
24+
}
25+
26+
struct Logger
27+
{
28+
const std::string prefix;
29+
const char* color;
30+
std::ostream& out;
31+
32+
Logger(std::string prefix, const char* color, std::ostream& out = std::cerr)
33+
: prefix(std::move(prefix)), color(color), out(out)
34+
{
35+
}
36+
37+
template <typename T>
38+
void log(const T& msg) const
39+
{
40+
out << msg << "\n";
41+
}
42+
43+
void logStack(const nlohmann::json& s) const
44+
{
45+
std::string action = s["action"];
46+
std::string msg;
47+
48+
if (action == "Create") {
49+
msg = std::format("{}[CREATE]:{}{}[{}]:{} {}", Color::CYAN, Color::RESET, Color::CYAN, s["type"].get<std::string>(), Color::RESET, s["args"].dump());
50+
} else if (action == "Apply") {
51+
msg = std::format("{}[APPLY]:[{}]:[{}]:{} {}", Color::MAGENTA, s["name"].get<std::string>(), s["input"].get<int>(), Color::RESET, s["args"].dump());
52+
} else if (action == "Wait") {
53+
msg = std::format("{}[WAIT]:{}{}", Color::MAGENTA, Color::RED, s["n"].get<int>());
54+
} else if (action == "Timestamp") {
55+
msg = std::format("{}[TIMESTAMP]:{} {} at frame {}", Color::GREEN, Color::RESET, s["name"].get<std::string>(), s["time"].get<size_t>());
56+
} else {
57+
msg = s.dump();
58+
}
59+
60+
log(msg);
61+
}
62+
};
63+
64+
inline Logger Debug{"Debug", Color::CYAN};
65+
}

src/core/Core.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "core/Core.hpp"
99

1010
#include <cstddef>
11+
#include <format>
1112
#include <iostream>
1213
#include <memory>
1314
#include <string>
@@ -20,6 +21,7 @@
2021
#include "input/media/WebImage.hpp"
2122
// #include "input/media/Video.hpp"
2223
#include "utils/Exception.hpp"
24+
#include "utils/Logger.hpp"
2325
#include "window/VulkanWidget.hpp"
2426

2527
VC::Core::Core(const argparse::ArgumentParser& parser, const Config& config)
@@ -75,9 +77,8 @@ std::string VC::Core::serializeScene()
7577
void VC::Core::executeStack()
7678
{
7779
for (auto& s : _stack) {
78-
if (_showstack) {
79-
std::cout << s << std::endl;
80-
}
80+
if (_showstack)
81+
VC::Debug.logStack(s);
8182

8283
if (s["action"] == "Create") {
8384
_inputs.push_back(Factory::inputs.at(s["type"])(s["args"]));

src/input/shape/BezierPath.cpp

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
#include "input/shape/BezierPath.hpp"
99

10+
#include <mapbox/earcut.hpp>
11+
1012
#include "vulkan/MeshFactory.hpp"
1113

1214
BezierPath::BezierPath(json::object_t&& args)
@@ -79,25 +81,21 @@ Mesh BezierPath::getMesh(const Metadata& meta, const Config& config)
7981
}
8082
}
8183

82-
// Fill triangulation — always occupies the full shape boundary.
84+
// Fill triangulation — earcut handles non-convex polygons correctly.
8385
if (fillColor[3] > 0 && poly.size() >= 3) {
84-
cv::Vec2f centroid{0.f, 0.f};
85-
for (const auto& p : poly) centroid += p;
86-
centroid *= (1.f / static_cast<float>(poly.size()));
86+
std::vector<std::vector<std::array<float, 2>>> polygon(1);
87+
polygon[0].reserve(poly.size());
88+
for (const auto& p : poly)
89+
polygon[0].push_back({p[0], p[1]});
8790

88-
uint16_t centerIdx = factory.vertexCount();
89-
factory.addVertex(centroid[0], centroid[1], fillColor);
91+
auto earIndices = mapbox::earcut<uint16_t>(polygon);
9092

9193
uint16_t firstPolyIdx = factory.vertexCount();
9294
for (const auto& p : poly)
9395
factory.addVertex(p[0], p[1], fillColor);
9496

95-
size_t polySize = poly.size();
96-
for (size_t i = 0; i < polySize; ++i) {
97-
factory.mesh.indices.push_back(centerIdx);
98-
factory.mesh.indices.push_back(firstPolyIdx + static_cast<uint16_t>(i));
99-
factory.mesh.indices.push_back(firstPolyIdx + static_cast<uint16_t>((i + 1) % polySize));
100-
}
97+
for (auto idx : earIndices)
98+
factory.mesh.indices.push_back(firstPolyIdx + idx);
10199
}
102100

103101
// Stroke rendered on top of fill, extruded inward so it stays within the boundary.

video.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
from videocode import *
66

77
# --- Marius' Test (Do not remove, just comment it, you should try it also)
8-
example0()
8+
example5()
9+
910

1011
# p = Plane()
1112
# s = Triangle().align(0, 0)

videocode/constants.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,12 @@
6060
GREEN = rgba(0, 255, 0)
6161
BLUE = rgba(0, 0, 255)
6262

63-
LIGHT_RED = rgba(255, 104, 104)
64-
LIGHT_GREEN = rgba(115, 255, 115)
65-
LIGHT_BLUE = rgba(132, 204, 255)
63+
RED_A = rgba("#FC6255")
64+
RED_B = rgba("#ED7F7B")
6665

67-
DARK_RED = rgba(255, 37, 37)
68-
DARK_GREEN = rgba(42, 255, 23)
69-
DARK_BLUE = rgba(32, 50, 255)
66+
GREEN_A = rgba("#9ADF8E")
7067

71-
BLUE_A = rgba("#58C4DD")
72-
RED_C = rgba("#FC6255")
68+
BLUE_A = rgba("#58C4DD") # light blue
69+
BLUE_B = rgba("#0B142B") # very dark blue
70+
BLUE_C = rgba("#69A5F1")
7371
# fmt: on

videocode/context.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ def __init__(self, *, interface: bool = False) -> None:
2525
# --- Align ---
2626
self.align: v2[number] = v2(0.5, 0.5)
2727

28-
# --- Scale ---
29-
self.scale: v2[number] = v2(1, 1)
30-
3128
# --- Rotation ---
3229
self.rotation: number = 0
3330

31+
# --- Scale ---
32+
self.scale: v2[number] = v2(1, 1)
33+
3434
# --- Opacity ---
3535
self.opacity: number = 255
3636

@@ -56,11 +56,11 @@ def __init__(self, *, interface: bool = False) -> None:
5656
"""
5757
Setting an Attribute will trigger an `apply(args(attr))`
5858
"""
59-
self.pendingSetattrStart: defaultable[sec] = default(0)
59+
self.pendingSetattrStart: sec = 0
6060
"""
6161
Keep start through setattr.
6262
"""
63-
self.pendingSetattrDuration: defaultable[sec] = default(1)
63+
self.pendingSetattrDuration: sec = 1
6464
"""
6565
Keep duration through setattr.
6666
"""
@@ -129,6 +129,6 @@ def timestamp(name: str) -> None:
129129
{
130130
"action": "Timestamp",
131131
"name": name,
132-
"time": Context.waitOffset,
132+
"time": Context.lastEverAffectedFrame,
133133
}
134134
)

videocode/input/group/Group.py

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,60 +5,80 @@
55

66

77
from videocode.input.input import *
8-
from videocode.input.offset.Offset import Offset
9-
from videocode.shader.vertexShader.position import position
108

119

1210
class Group(Input):
11+
# fmt: off
1312
"""
1413
A `Group` contains many inputs and will apply each transformations it gets to all of its inputs.
14+
15+
Also used to create bigger things that link many Inputs.
16+
You create a class that inherits from `Group`, setup your things and then super().__init__().
1517
"""
18+
# fmt: on
1619

1720
def __new__(cls, *args, **kwargs) -> Self:
1821
instance = object.__new__(cls)
1922
instance.meta = Metadata(interface=True)
2023
return instance
2124

2225
def __init__(self, *inputs: Input):
23-
self.inputs = [i for i in inputs]
26+
self._inputs = [i for i in inputs]
2427

2528
def addInput(self, *inputs: Input) -> Self:
2629
for i in inputs:
27-
self.inputs.append(i)
30+
self._inputs.append(i)
2831
return self
2932

3033
def flush(self) -> Self:
3134
"""
3235
Appends the `frames` of `self` to the `timeline`.
3336
"""
34-
for i in self.inputs:
37+
for i in self._inputs:
3538
i.flush()
3639

3740
return self
3841

39-
def apply(self, *shaders: IShader, start: defaultable[sec] = default(0), duration: defaultable[sec] = default(1)) -> Self:
42+
def apply(self, *shaders: IShader, start: sec = 0, duration: sec = 1) -> Self:
4043
"""
4144
Applies the `Transformations` `ts` to all the `Inputs` of the `Group`.
4245
"""
4346
for s in shaders:
44-
for i in self.inputs:
47+
for i in self._inputs:
4548
i.apply(copy.deepcopy(s), start=start, duration=duration)
4649

4750
return self
4851

4952
def waitForOthers(self, n: sec = 0, updateContext=False) -> Self:
50-
lastAffectedFramePlusN = max(i.meta.lastAffectedFrame for i in self.inputs) + int(n * FRAMERATE)
53+
lastAffectedFramePlusN = max(i.meta.lastAffectedFrame for i in self._inputs) + int(n * FRAMERATE)
5154

52-
for i in self.inputs:
55+
for i in self._inputs:
5356
i.waitTo(lastAffectedFramePlusN)
5457

5558
if updateContext and lastAffectedFramePlusN >= Context.lastEverAffectedFrame:
5659
Context.waitOffset = Context.lastEverAffectedFrame = lastAffectedFramePlusN
5760

5861
return self
5962

63+
def _broadcast(self, name: str, *args, **kwargs) -> Self:
64+
for child in self._inputs:
65+
getattr(child, name)(*args, **kwargs)
66+
return self
67+
68+
# fmt: off
69+
def moveTo(self, *args, **kwargs) -> Self: return self._broadcast('moveTo', *args, **kwargs)
70+
def moveBy(self, *args, **kwargs) -> Self: return self._broadcast('moveBy', *args, **kwargs)
71+
def scaleTo(self, *args, **kwargs) -> Self: return self._broadcast('scaleTo', *args, **kwargs)
72+
def scaleBy(self, *args, **kwargs) -> Self: return self._broadcast('scaleBy', *args, **kwargs)
73+
def rotateTo(self, *args, **kwargs) -> Self: return self._broadcast('rotateTo', *args, **kwargs)
74+
def rotateBy(self, *args, **kwargs) -> Self: return self._broadcast('rotateBy', *args, **kwargs)
75+
def alignTo(self, *args, **kwargs) -> Self: return self._broadcast('alignTo', *args, **kwargs)
76+
def fadeIn(self, *args, **kwargs) -> Self: return self._broadcast('fadeIn', *args, **kwargs)
77+
def fadeOut(self, *args, **kwargs) -> Self: return self._broadcast('fadeOut', *args, **kwargs)
78+
# fmt: on
79+
6080
def __str__(self) -> str:
61-
return "".join(f"idx=[{idx}], i=[{type(i).__name__}]]\n" for idx, i in enumerate(self.inputs))
81+
return "".join(f"idx=[{idx}], i=[{type(i).__name__}]]\n" for idx, i in enumerate(self._inputs))
6282

6383
def __repr__(self) -> str:
6484
return self.__str__()

0 commit comments

Comments
 (0)