Merge branch 'main' into release
This commit is contained in:
commit
e65aac496c
15
Changelog.md
15
Changelog.md
|
|
@ -1,3 +1,18 @@
|
||||||
|
# 0.7.0-alpha:
|
||||||
|
New features:
|
||||||
|
- Keyframes can now have both motion and shape tweens on the same frame
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- Tweens are now indicated with colored lines
|
||||||
|
- Tweens are now attached to keyframes rather than the frames in between them
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
- Fix paint bucket coordinates being incorrect inside of movie clips
|
||||||
|
- Fix paint bucket not working for large shapes and shapes whose internal coordinates crossed 0,0
|
||||||
|
- Fixed dragging frames breaking tweens
|
||||||
|
- Fixed logs being inaccessible on macOS
|
||||||
|
- Fixed right-click causing a menu with "Reload" to appear which would reset the application
|
||||||
|
|
||||||
# 0.6.18-alpha:
|
# 0.6.18-alpha:
|
||||||
New features:
|
New features:
|
||||||
- Errors and debug messages are now logged to a file
|
- Errors and debug messages are now logged to a file
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ use tauri_plugin_log::{Target, TargetKind};
|
||||||
use log::{trace, info, debug, warn, error};
|
use log::{trace, info, debug, warn, error};
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
|
use tauri::Manager;
|
||||||
|
|
||||||
|
|
||||||
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
||||||
|
|
@ -50,9 +51,9 @@ pub fn run() {
|
||||||
.targets([
|
.targets([
|
||||||
Target::new(TargetKind::Stdout),
|
Target::new(TargetKind::Stdout),
|
||||||
// LogDir locations:
|
// LogDir locations:
|
||||||
// Linux: /home/user/.local/share/org.lightningbeam.app/logs
|
// Linux: /home/user/.local/share/org.lightningbeam.core/logs
|
||||||
// macOS: /Users/user/Library/Logs/org.lightningbeam.app/logs
|
// macOS: /Users/user/Library/Logs/org.lightningbeam.core/logs
|
||||||
// Windows: C:\Users\user\AppData\Local\org.lightningbeam.app\logs
|
// Windows: C:\Users\user\AppData\Local\org.lightningbeam.core\logs
|
||||||
Target::new(TargetKind::LogDir { file_name: Some("logs".to_string()) }),
|
Target::new(TargetKind::LogDir { file_name: Some("logs".to_string()) }),
|
||||||
Target::new(TargetKind::Webview),
|
Target::new(TargetKind::Webview),
|
||||||
])
|
])
|
||||||
|
|
@ -62,6 +63,15 @@ pub fn run() {
|
||||||
.plugin(tauri_plugin_fs::init())
|
.plugin(tauri_plugin_fs::init())
|
||||||
.plugin(tauri_plugin_shell::init())
|
.plugin(tauri_plugin_shell::init())
|
||||||
.invoke_handler(tauri::generate_handler![greet, trace, debug, info, warn, error])
|
.invoke_handler(tauri::generate_handler![greet, trace, debug, info, warn, error])
|
||||||
|
.setup(|app| {
|
||||||
|
#[cfg(debug_assertions)] // only include this code on debug builds
|
||||||
|
{
|
||||||
|
let window = app.get_webview_window("main").unwrap();
|
||||||
|
window.open_devtools();
|
||||||
|
window.close_devtools();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
tracing_subscriber::fmt().with_env_filter(EnvFilter::new(format!("{}=trace", pkg_name))).init();
|
tracing_subscriber::fmt().with_env_filter(EnvFilter::new(format!("{}=trace", pkg_name))).init();
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "Lightningbeam",
|
"productName": "Lightningbeam",
|
||||||
"version": "0.6.18-alpha",
|
"version": "0.7.0-alpha",
|
||||||
"identifier": "org.lightningbeam.app",
|
"identifier": "org.lightningbeam.core",
|
||||||
"build": {
|
"build": {
|
||||||
"frontendDist": "../src"
|
"frontendDist": "../src"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
1275
src/main.js
1275
src/main.js
File diff suppressed because it is too large
Load Diff
50
src/utils.js
50
src/utils.js
|
|
@ -145,20 +145,40 @@ function generateWaveform(img, buffer, imgHeight, frameWidth, framesPerSecond) {
|
||||||
img.src = dataUrl;
|
img.src = dataUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function multiplyMatrices(a, b) {
|
||||||
|
let result = [
|
||||||
|
[0, 0, 0],
|
||||||
|
[0, 0, 0],
|
||||||
|
[0, 0, 0]
|
||||||
|
];
|
||||||
|
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
for (let j = 0; j < 3; j++) {
|
||||||
|
for (let k = 0; k < 3; k++) {
|
||||||
|
result[i][j] += a[i][k] * b[k][j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function growBoundingBox(bboxa, bboxb) {
|
||||||
|
bboxa.x.min = Math.min(bboxa.x.min, bboxb.x.min);
|
||||||
|
bboxa.y.min = Math.min(bboxa.y.min, bboxb.y.min);
|
||||||
|
bboxa.x.max = Math.max(bboxa.x.max, bboxb.x.max);
|
||||||
|
bboxa.y.max = Math.max(bboxa.y.max, bboxb.y.max);
|
||||||
|
}
|
||||||
|
|
||||||
function floodFillRegion(
|
function floodFillRegion(
|
||||||
startPoint,
|
startPoint,
|
||||||
epsilon,
|
epsilon,
|
||||||
offset, // TODO: this needs to be a generalized transform
|
|
||||||
fileWidth,
|
fileWidth,
|
||||||
fileHeight,
|
fileHeight,
|
||||||
context,
|
context,
|
||||||
debugPoints,
|
debugPoints,
|
||||||
debugPaintbucket) {
|
debugPaintbucket) {
|
||||||
// Helper function to check if the point is at the boundary of the region
|
|
||||||
function isBoundaryPoint(point) {
|
|
||||||
return point.x <= offset.x || point.x >= offset.x + fileWidth ||
|
|
||||||
point.y <= offset.y || point.y >= offset.y + fileHeight;
|
|
||||||
}
|
|
||||||
let halfEpsilon = epsilon/2
|
let halfEpsilon = epsilon/2
|
||||||
|
|
||||||
// Helper function to check if a point is near any curve in the shape
|
// Helper function to check if a point is near any curve in the shape
|
||||||
|
|
@ -186,6 +206,22 @@ function floodFillRegion(
|
||||||
const visited = new Set();
|
const visited = new Set();
|
||||||
const stack = [startPoint];
|
const stack = [startPoint];
|
||||||
const regionPoints = [];
|
const regionPoints = [];
|
||||||
|
let bbox;
|
||||||
|
if (shapes.length>0) {
|
||||||
|
bbox = shapes[0].boundingBox
|
||||||
|
} else {
|
||||||
|
throw new Error("No shapes in layer")
|
||||||
|
}
|
||||||
|
for (const shape of shapes) {
|
||||||
|
growBoundingBox(bbox, shape.boundingBox)
|
||||||
|
}
|
||||||
|
// Helper function to check if the point is at the boundary of the region
|
||||||
|
function isBoundaryPoint(point) {
|
||||||
|
return point.x <= bbox.x.min - 100 || point.x >= bbox.x.max + 100 ||
|
||||||
|
point.y <= bbox.y.min - 100 || point.y >= bbox.y.max + 100;
|
||||||
|
return point.x <= offset.x || point.x >= offset.x + fileWidth ||
|
||||||
|
point.y <= offset.y || point.y >= offset.y + fileHeight;
|
||||||
|
}
|
||||||
|
|
||||||
// Begin the flood fill process
|
// Begin the flood fill process
|
||||||
while (stack.length > 0) {
|
while (stack.length > 0) {
|
||||||
|
|
@ -872,7 +908,9 @@ export {
|
||||||
lerpColor,
|
lerpColor,
|
||||||
camelToWords,
|
camelToWords,
|
||||||
generateWaveform,
|
generateWaveform,
|
||||||
|
growBoundingBox,
|
||||||
floodFillRegion,
|
floodFillRegion,
|
||||||
|
multiplyMatrices,
|
||||||
getShapeAtPoint,
|
getShapeAtPoint,
|
||||||
hslToRgb,
|
hslToRgb,
|
||||||
hsvToRgb,
|
hsvToRgb,
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,39 @@
|
||||||
import { clamp, drawCheckerboardBackground, hslToRgb, hsvToRgb, rgbToHex } from "./utils.js"
|
import { clamp, drawCheckerboardBackground, hslToRgb, hsvToRgb, rgbToHex } from "./utils.js"
|
||||||
|
|
||||||
|
function growBoundingBox(bboxa, bboxb) {
|
||||||
|
bboxa.x.min = Math.min(bboxa.x.min, bboxb.x.min);
|
||||||
|
bboxa.y.min = Math.min(bboxa.y.min, bboxb.y.min);
|
||||||
|
bboxa.x.max = Math.max(bboxa.x.max, bboxb.x.max);
|
||||||
|
bboxa.y.max = Math.max(bboxa.y.max, bboxb.y.max);
|
||||||
|
}
|
||||||
|
|
||||||
class Widget {
|
class Widget {
|
||||||
constructor(x, y) {
|
constructor(x, y) {
|
||||||
this._globalEvents = new Set()
|
this._globalEvents = new Set()
|
||||||
this.x = x
|
this.x = x
|
||||||
this.y = y
|
this.y = y
|
||||||
|
this.scale_x = 1
|
||||||
|
this.scale_y = 1
|
||||||
|
this.rotation = 0
|
||||||
this.children = []
|
this.children = []
|
||||||
}
|
}
|
||||||
handleMouseEvent(eventType, x, y) {
|
handleMouseEvent(eventType, x, y) {
|
||||||
for (let child of this.children) {
|
for (let child of this.children) {
|
||||||
if (child.hitTest(x, y) || child._globalEvents.has(eventType)) {
|
// Adjust for translation
|
||||||
child.handleMouseEvent(eventType, x-child.x, y-child.y)
|
const dx = x - child.x;
|
||||||
|
const dy = y - child.y;
|
||||||
|
|
||||||
|
// Apply inverse rotation
|
||||||
|
const cosTheta = Math.cos(child.rotation);
|
||||||
|
const sinTheta = Math.sin(child.rotation);
|
||||||
|
|
||||||
|
// Rotate coordinates to child's local space
|
||||||
|
const rotatedX = dx * cosTheta + dy * sinTheta;
|
||||||
|
const rotatedY = -dx * sinTheta + dy * cosTheta;
|
||||||
|
|
||||||
|
// First, perform hit test using original (global) coordinates
|
||||||
|
if (child.hitTest(rotatedX, rotatedY) || child._globalEvents.has(eventType)) {
|
||||||
|
child.handleMouseEvent(eventType, rotatedX, rotatedY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const eventTypes = [
|
const eventTypes = [
|
||||||
|
|
@ -26,22 +49,45 @@ class Widget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
hitTest(x, y) {
|
hitTest(x, y) {
|
||||||
if ((x >= this.x) && (x <= this.x+this.width) &&
|
// if ((x >= this.x) && (x <= this.x+this.width) &&
|
||||||
(y >= this.y) && (y <= this.y+this.height)) {
|
// (y >= this.y) && (y <= this.y+this.height)) {
|
||||||
|
if ((x>=0) && (x <= this.width) && (y >= 0) && (y <= this.height)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
bbox() {
|
||||||
|
let bbox;
|
||||||
|
if (this.children.length > 0) {
|
||||||
|
if (!bbox) {
|
||||||
|
bbox = structuredClone(this.children[0].bbox());
|
||||||
|
}
|
||||||
|
for (let child of this.children) {
|
||||||
|
growBoundingBox(bbox, child.bbox());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bbox == undefined) {
|
||||||
|
bbox = { x: { min: 0, max: 0 }, y: { min: 0, max: 0 } };
|
||||||
|
}
|
||||||
|
bbox.x.max *= this.scale_x;
|
||||||
|
bbox.y.max *= this.scale_y;
|
||||||
|
bbox.x.min += this.x;
|
||||||
|
bbox.x.max += this.x;
|
||||||
|
bbox.y.min += this.y;
|
||||||
|
bbox.y.max += this.y;
|
||||||
|
return bbox;
|
||||||
|
}
|
||||||
draw(ctx) {
|
draw(ctx) {
|
||||||
for (let child of this.children) {
|
for (let child of this.children) {
|
||||||
const transform = ctx.getTransform()
|
const transform = ctx.getTransform()
|
||||||
ctx.translate(child.x, child.y)
|
ctx.translate(child.x, child.y)
|
||||||
|
ctx.scale(child.scale_x, child.scale_y)
|
||||||
|
ctx.rotate(child.rotation)
|
||||||
child.draw(ctx)
|
child.draw(ctx)
|
||||||
ctx.setTransform(transform)
|
ctx.setTransform(transform)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class HueSelectionBar extends Widget {
|
class HueSelectionBar extends Widget {
|
||||||
constructor(width, height, x, y, colorCvs) {
|
constructor(width, height, x, y, colorCvs) {
|
||||||
super(x, y)
|
super(x, y)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue