Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ members = [
"desktop/platform/mac",
"desktop/platform/win",
"editor",
"frontend/wasm",
"libraries/ai-models",
"frontend/wrapper",
"libraries/dyn-any",
"libraries/math-parser",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes

// Send the overlays message to the overlays message handler
self.overlays_message_handler
.process_message(message, responses, OverlaysMessageContext { visibility_settings, viewport });
.process_message(message, responses, OverlaysMessageContext { visibility_settings, viewport, animation_time: ipp.time as f64 });
}
DocumentMessage::PropertiesPanel(message) => {
let context = PropertiesPanelMessageContext {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,8 @@ fn apply_usvg_stroke(stroke: &usvg::Stroke, modify_inputs: &mut ModifyInputsCont
if let usvg::Paint::Color(color) = &stroke.paint() {
modify_inputs.stroke_set(Stroke {
color: Some(usvg_color(*color, stroke.opacity().get())),
//Added the gradient field to the Stroke struct
gradient: None,
weight: stroke.width().get() as f64,
dash_lengths: stroke.dasharray().as_ref().map(|lengths| lengths.iter().map(|&length| length as f64).collect()).unwrap_or_default(),
dash_offset: stroke.dashoffset() as f64,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use crate::messages::prelude::*;
pub struct OverlaysMessageContext<'a> {
pub visibility_settings: OverlaysVisibilitySettings,
pub viewport: &'a ViewportMessageHandler,
/// Current time in milliseconds passed from the input preprocessor, used to drive overlay animations (e.g. marching ants).
pub animation_time: f64,
}

#[derive(Debug, Clone, Default, ExtractField)]
Expand All @@ -19,7 +21,7 @@ pub struct OverlaysMessageHandler {
#[message_handler_data]
impl MessageHandler<OverlaysMessage, OverlaysMessageContext<'_>> for OverlaysMessageHandler {
fn process_message(&mut self, message: OverlaysMessage, responses: &mut VecDeque<Message>, context: OverlaysMessageContext) {
let OverlaysMessageContext { visibility_settings, viewport, .. } = context;
let OverlaysMessageContext { visibility_settings, viewport, animation_time } = context;

match message {
#[cfg(target_family = "wasm")]
Expand Down Expand Up @@ -55,13 +57,15 @@ impl MessageHandler<OverlaysMessage, OverlaysMessageContext<'_>> for OverlaysMes
render_context: canvas_context.clone(),
visibility_settings: visibility_settings.clone(),
viewport: *viewport,
animation_time,
},
});
for provider in &self.overlay_providers {
responses.add(provider(OverlayContext {
render_context: canvas_context.clone(),
visibility_settings: visibility_settings.clone(),
viewport: *viewport,
animation_time,
}));
}
}
Expand All @@ -70,7 +74,7 @@ impl MessageHandler<OverlaysMessage, OverlaysMessageContext<'_>> for OverlaysMes
OverlaysMessage::Draw => {
use super::utility_types::OverlayContext;

let overlay_context = OverlayContext::new(*viewport, visibility_settings);
let overlay_context = OverlayContext::new(*viewport, visibility_settings, animation_time);

if visibility_settings.all() {
responses.add(DocumentMessage::GridOverlays { context: overlay_context.clone() });
Expand All @@ -83,7 +87,7 @@ impl MessageHandler<OverlaysMessage, OverlaysMessageContext<'_>> for OverlaysMes
}
#[cfg(all(not(target_family = "wasm"), test))]
OverlaysMessage::Draw => {
let _ = (responses, visibility_settings, viewport);
let _ = (responses, visibility_settings, viewport, animation_time);
}
OverlaysMessage::AddProvider { provider: message } => {
self.overlay_providers.insert(message);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ pub struct OverlayContext {
internal: Arc<Mutex<OverlayContextInternal>>,
pub viewport: ViewportMessageHandler,
pub visibility_settings: OverlaysVisibilitySettings,
/// Current time in milliseconds, used to animate effects like marching ants.
pub animation_time: f64,
}

impl Clone for OverlayContext {
Expand All @@ -181,6 +183,7 @@ impl Clone for OverlayContext {
internal: self.internal.clone(),
viewport: self.viewport,
visibility_settings,
animation_time: self.animation_time,
}
}
}
Expand All @@ -198,6 +201,7 @@ impl std::fmt::Debug for OverlayContext {
.field("scene", &"Scene { ... }")
.field("viewport", &self.viewport)
.field("visibility_settings", &self.visibility_settings)
.field("animation_time", &self.animation_time)
.finish()
}
}
Expand All @@ -209,6 +213,7 @@ impl Default for OverlayContext {
internal: Mutex::new(OverlayContextInternal::default()).into(),
viewport: ViewportMessageHandler::default(),
visibility_settings: OverlaysVisibilitySettings::default(),
animation_time: 0.,
}
}
}
Expand All @@ -220,7 +225,7 @@ impl core::hash::Hash for OverlayContext {

impl OverlayContext {
#[allow(dead_code)]
pub(super) fn new(viewport: ViewportMessageHandler, visibility_settings: OverlaysVisibilitySettings) -> Self {
pub(super) fn new(viewport: ViewportMessageHandler, visibility_settings: OverlaysVisibilitySettings, animation_time: f64) -> Self {
Self {
internal: Arc::new(Mutex::new(OverlayContextInternal::new(viewport, visibility_settings))),
viewport,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ pub struct OverlayContext {
pub render_context: web_sys::CanvasRenderingContext2d,
pub viewport: ViewportMessageHandler,
pub visibility_settings: OverlaysVisibilitySettings,
/// Current time in milliseconds (e.g. from `js_sys::Date::now()`), used to animate effects like marching ants.
pub animation_time: f64,
}
// Message hashing isn't used but is required by the message system macros
impl core::hash::Hash for OverlayContext {
Expand Down
41 changes: 38 additions & 3 deletions editor/src/messages/tool/tool_messages/select_tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,8 @@ struct SelectToolData {
snap_candidates: Vec<SnapCandidatePoint>,
auto_panning: AutoPanning,
drag_start_center: ViewportPosition,
/// Whether the tool is currently subscribed to animation frame events to drive the marching ants animation.
marching_ants_subscribed: bool,
}

impl SelectToolData {
Expand All @@ -421,6 +423,27 @@ impl SelectToolData {
}
}
}
/// Subscribe to per-frame animation ticks so the marching ants selection border animates continuously.
fn start_marching_ants(&mut self, responses: &mut VecDeque<Message>) {
if !self.marching_ants_subscribed {
self.marching_ants_subscribed = true;
responses.add(BroadcastMessage::SubscribeEvent {
on: EventMessage::AnimationFrame,
send: Box::new(OverlaysMessage::Draw.into()),
});
}
}

/// Unsubscribe from per-frame animation ticks when the selection box is no longer being drawn.
fn stop_marching_ants(&mut self, responses: &mut VecDeque<Message>) {
if self.marching_ants_subscribed {
self.marching_ants_subscribed = false;
responses.add(BroadcastMessage::UnsubscribeEvent {
on: EventMessage::AnimationFrame,
send: Box::new(OverlaysMessage::Draw.into()),
});
}
}

pub fn selection_quad(&self) -> Quad {
let bbox = self.selection_box();
Expand Down Expand Up @@ -965,10 +988,17 @@ impl Fsm for SelectToolFsmState {
let fill_color = Some(COLOR_OVERLAY_BLUE_05);

let polygon = &tool_data.lasso_polygon;
// Animate the dash offset to produce the "marching ants" effect. The dash pattern repeats every 8 px (4 px dash + 4 px gap),
// so wrapping the time to [0, 8) via the modulo gives a smooth, continuously looping animation.
// MARCHING_ANTS_PIXELS_PER_SECOND controls how fast the dashes march around the selection border.
const MARCHING_ANTS_PIXELS_PER_SECOND: f64 = 100.; // How many pixels the pattern advances per second
const MARCHING_ANTS_PERIOD: f64 = 8.; // One full cycle = dash length (4 px) + gap length (4 px)
let marching_ants_offset = (overlay_context.animation_time / 1000. * MARCHING_ANTS_PIXELS_PER_SECOND) % MARCHING_ANTS_PERIOD;


match (selection_shape, current_selection_mode) {
(SelectionShapeType::Box, SelectionMode::Enclosed) => overlay_context.dashed_quad(quad, None, fill_color, Some(4.), Some(4.), Some(0.5)),
(SelectionShapeType::Lasso, SelectionMode::Enclosed) => overlay_context.dashed_polygon(polygon, None, fill_color, Some(4.), Some(4.), Some(0.5)),
(SelectionShapeType::Box, SelectionMode::Enclosed) => overlay_context.dashed_quad(quad, None, fill_color, Some(4.), Some(4.), Some(marching_ants_offset)),
(SelectionShapeType::Lasso, SelectionMode::Enclosed) => overlay_context.dashed_polygon(polygon, None, fill_color, Some(4.), Some(4.), Some(marching_ants_offset)),
(SelectionShapeType::Box, _) => overlay_context.quad(quad, None, fill_color),
(SelectionShapeType::Lasso, _) => overlay_context.polygon(polygon, None, fill_color),
}
Expand Down Expand Up @@ -1125,6 +1155,8 @@ impl Fsm for SelectToolFsmState {
}
} else {
let selection_shape = if input.keyboard.key(lasso_select) { SelectionShapeType::Lasso } else { SelectionShapeType::Box };
// Subscribe to animation frames so the marching ants selection border animates continuously.
tool_data.start_marching_ants(responses);
SelectToolFsmState::Drawing { selection_shape, has_drawn: false }
}
};
Expand Down Expand Up @@ -1556,7 +1588,8 @@ impl Fsm for SelectToolFsmState {
}

tool_data.lasso_polygon.clear();

// Unsubscribe from animation frames now that the selection box is finalized.
tool_data.stop_marching_ants(responses);
responses.add(OverlaysMessage::Draw);

let selection = tool_data.nested_selection_behavior;
Expand Down Expand Up @@ -1603,6 +1636,8 @@ impl Fsm for SelectToolFsmState {
responses.add(DocumentMessage::AbortTransaction);
tool_data.snap_manager.cleanup(responses);
tool_data.lasso_polygon.clear();
// Unsubscribe from marching ants animation in case we were in Drawing state.
tool_data.stop_marching_ants(responses);
responses.add(OverlaysMessage::Draw);

let selection = tool_data.nested_selection_behavior;
Expand Down
14 changes: 14 additions & 0 deletions libraries/ai-models/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "ai-models"
version = "0.0.0"
rust-version = "1.88"
edition = "2024"
authors = ["Graphite Authors <contact@graphite.art>"]
description = "Model Registry & Metadata Schema for Graphite AI capabilities"
license = "Apache-2.0"

[dependencies]
serde = { workspace = true }
serde_json = { workspace = true }
thiserror = { workspace = true }
log = { workspace = true }
14 changes: 14 additions & 0 deletions libraries/ai-models/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//! # AI Model Registry & Metadata Schema
//!
//! This crate is the central nervous system for Graphite's AI capabilities.
//! It manages how the editor identifies, validates, and prepares to launch
//! various machine learning models through three logical layers:
//!
//! 1. **[`ModelManifest`]** – the serialisable "identity card" of a model.
//! 2. **[`License`]** – a safety gate that blocks non-permissive models.
//! 3. **[`ModelRegistry`]** – the centralised service that tracks every model's lifecycle.
pub mod manifest;
pub mod registry;

pub use manifest::{License, ModelManifest, TensorShape};
pub use registry::{ModelRegistry, ModelStatus, RegistryError};
Loading