Merge branch 'main' into release

This commit is contained in:
Skyler Lehmkuhl 2025-01-10 19:37:47 -05:00
commit e65aac496c
6 changed files with 1076 additions and 340 deletions

View File

@ -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

View 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();

View File

@ -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"
}, },

File diff suppressed because it is too large Load Diff

View File

@ -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,

View File

@ -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)