Skip to content
Merged
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
4 changes: 4 additions & 0 deletions editor/src/dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub struct Dispatcher {
#[derive(Debug, Default)]
pub struct DispatcherMessageHandlers {
animation_message_handler: AnimationMessageHandler,
app_window_message_handler: AppWindowMessageHandler,
broadcast_message_handler: BroadcastMessageHandler,
debug_message_handler: DebugMessageHandler,
dialog_message_handler: DialogMessageHandler,
Expand Down Expand Up @@ -129,6 +130,9 @@ impl Dispatcher {
Message::Animation(message) => {
self.message_handlers.animation_message_handler.process_message(message, &mut queue, ());
}
Message::AppWindow(message) => {
self.message_handlers.app_window_message_handler.process_message(message, &mut queue, ());
}
Message::Broadcast(message) => self.message_handlers.broadcast_message_handler.process_message(message, &mut queue, ()),
Message::Debug(message) => {
self.message_handlers.debug_message_handler.process_message(message, &mut queue, ());
Expand Down
9 changes: 9 additions & 0 deletions editor/src/messages/app_window/app_window_message.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use crate::messages::prelude::*;

#[impl_message(Message, AppWindow)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum AppWindowMessage {
AppWindowMinimize,
AppWindowMaximize,
AppWindowClose,
}
52 changes: 52 additions & 0 deletions editor/src/messages/app_window/app_window_message_handler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use crate::messages::app_window::AppWindowMessage;
use crate::messages::prelude::*;
use graphite_proc_macros::{ExtractField, message_handler_data};

#[derive(Debug, Clone, Default, ExtractField)]
pub struct AppWindowMessageHandler {
platform: AppWindowPlatform,
maximized: bool,
viewport_hole_punch_active: bool,
}

#[message_handler_data]
impl MessageHandler<AppWindowMessage, ()> for AppWindowMessageHandler {
fn process_message(&mut self, message: AppWindowMessage, responses: &mut std::collections::VecDeque<Message>, _: ()) {
match message {
AppWindowMessage::AppWindowMinimize => {
self.platform = if self.platform == AppWindowPlatform::Mac {
AppWindowPlatform::Windows
} else {
AppWindowPlatform::Mac
};
responses.add(FrontendMessage::UpdatePlatform { platform: self.platform });
}
AppWindowMessage::AppWindowMaximize => {
self.maximized = !self.maximized;
responses.add(FrontendMessage::UpdateMaximized { maximized: self.maximized });

self.viewport_hole_punch_active = !self.viewport_hole_punch_active;
responses.add(FrontendMessage::UpdateViewportHolePunch {
active: self.viewport_hole_punch_active,
});
}
AppWindowMessage::AppWindowClose => {
self.platform = AppWindowPlatform::Web;
responses.add(FrontendMessage::UpdatePlatform { platform: self.platform });
}
}
}

fn actions(&self) -> ActionList {
actions!(AppWindowMessageDiscriminant;)
}
}

#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum AppWindowPlatform {
#[default]
Web,
Windows,
Mac,
Linux,
}
7 changes: 7 additions & 0 deletions editor/src/messages/app_window/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
mod app_window_message;
pub mod app_window_message_handler;

#[doc(inline)]
pub use app_window_message::{AppWindowMessage, AppWindowMessageDiscriminant};
#[doc(inline)]
pub use app_window_message_handler::AppWindowMessageHandler;
10 changes: 10 additions & 0 deletions editor/src/messages/frontend/frontend_message.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::utility_types::{FrontendDocumentDetails, MouseCursorIcon};
use crate::messages::app_window::app_window_message_handler::AppWindowPlatform;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::node_graph::utility_types::{
BoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeType, Transform,
Expand Down Expand Up @@ -309,4 +310,13 @@ pub enum FrontendMessage {
layout_target: LayoutTarget,
diff: Vec<WidgetDiff>,
},
UpdatePlatform {
platform: AppWindowPlatform,
},
UpdateMaximized {
maximized: bool,
},
UpdateViewportHolePunch {
active: bool,
},
}
2 changes: 2 additions & 0 deletions editor/src/messages/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ pub enum Message {
#[child]
Animation(AnimationMessage),
#[child]
AppWindow(AppWindowMessage),
#[child]
Broadcast(BroadcastMessage),
#[child]
Debug(DebugMessage),
Expand Down
1 change: 1 addition & 0 deletions editor/src/messages/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! The root-level messages forming the first layer of the message system architecture.

pub mod animation;
pub mod app_window;
pub mod broadcast;
pub mod debug;
pub mod dialog;
Expand Down
6 changes: 3 additions & 3 deletions editor/src/messages/portfolio/portfolio_message_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,15 +107,15 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio

let compatible_type = first_layer.and_then(|layer| {
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &document.network_interface);
graph_layer.horizontal_layer_flow().nth(1).and_then(|node_id| {
graph_layer.horizontal_layer_flow().nth(1).map(|node_id| {
let (output_type, _) = document.network_interface.output_type(&node_id, 0, &[]);
Some(format!("type:{}", output_type.nested_type()))
format!("type:{}", output_type.nested_type())
})
});

let is_compatible = compatible_type.as_deref() == Some("type:Instances<VectorData>");

let is_modifiable = first_layer.map_or(false, |layer| {
let is_modifiable = first_layer.is_some_and(|layer| {
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &document.network_interface);
matches!(graph_layer.find_input("Path", 1), Some(TaggedValue::VectorModification(_)))
});
Expand Down
2 changes: 1 addition & 1 deletion editor/src/messages/portfolio/utility_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ impl From<String> for PanelType {
"Layers" => PanelType::Layers,
"Properties" => PanelType::Properties,
"Spreadsheet" => PanelType::Spreadsheet,
_ => panic!("Unknown panel type: {}", value),
_ => panic!("Unknown panel type: {value}"),
}
}
}
1 change: 1 addition & 0 deletions editor/src/messages/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub use crate::utility_traits::{ActionList, AsMessage, HierarchicalTree, Message
pub use crate::utility_types::{DebugMessageTree, MessageData};
// Message, MessageData, MessageDiscriminant, MessageHandler
pub use crate::messages::animation::{AnimationMessage, AnimationMessageDiscriminant, AnimationMessageHandler};
pub use crate::messages::app_window::{AppWindowMessage, AppWindowMessageDiscriminant, AppWindowMessageHandler};
pub use crate::messages::broadcast::{BroadcastMessage, BroadcastMessageDiscriminant, BroadcastMessageHandler};
pub use crate::messages::debug::{DebugMessage, DebugMessageDiscriminant, DebugMessageHandler};
pub use crate::messages::dialog::export_dialog::{ExportDialogMessage, ExportDialogMessageContext, ExportDialogMessageDiscriminant, ExportDialogMessageHandler};
Expand Down
8 changes: 7 additions & 1 deletion frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,17 @@
<noscript>JavaScript is required</noscript>
<style>
body {
background: #222;
height: 100%;
margin: 0;
}

body::before {
content: "";
position: absolute;
inset: 0;
background: #222;
}

body::after {
content: "";
display: block;
Expand Down
14 changes: 12 additions & 2 deletions frontend/src/components/Editor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import { createLocalizationManager } from "@graphite/io-managers/localization";
import { createPanicManager } from "@graphite/io-managers/panic";
import { createPersistenceManager } from "@graphite/io-managers/persistence";
import { createAppWindowState } from "@graphite/state-providers/app-window";
import { createDialogState } from "@graphite/state-providers/dialog";
import { createDocumentState } from "@graphite/state-providers/document";
import { createFontsState } from "@graphite/state-providers/fonts";
Expand Down Expand Up @@ -36,6 +37,8 @@
setContext("nodeGraph", nodeGraph);
let portfolio = createPortfolioState(editor);
setContext("portfolio", portfolio);
let appWindow = createAppWindowState(editor);
setContext("appWindow", appWindow);

// Initialize managers, which are isolated systems that subscribe to backend messages to link them to browser API functionality (like JS events, IndexedDB, etc.)
createClipboardManager(editor);
Expand All @@ -58,10 +61,11 @@
});
</script>

<MainWindow />
<MainWindow platform={$appWindow.platform} maximized={$appWindow.maximized} viewportHolePunch={$appWindow.viewportHolePunch} />

<style lang="scss" global>
// Disable the spinning loading indicator
body::before,
body::after {
content: none !important;
}
Expand Down Expand Up @@ -206,10 +210,16 @@
height: 100%;
background: var(--color-2-mildblack);
overscroll-behavior: none;
-webkit-user-select: none; // Required as of Safari 15.0 (Graphite's minimum version) through the latest release
-webkit-user-select: none; // Still required by Safari as of 2025
user-select: none;
}

// Needed for the viewport hole punch on desktop
html:has(body > .viewport-hole-punch),
body:has(> .viewport-hole-punch) {
background: none;
}

// The default value of `auto` from the CSS spec is a footgun with flexbox layouts:
// https://stackoverflow.com/questions/36247140/why-dont-flex-items-shrink-past-content-size
* {
Expand Down
52 changes: 30 additions & 22 deletions frontend/src/components/panels/Document.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
UpdateMouseCursor,
isWidgetSpanRow,
} from "@graphite/messages";
import type { AppWindowState } from "@graphite/state-providers/app-window";
import type { DocumentState } from "@graphite/state-providers/document";
import { textInputCleanup } from "@graphite/utility-functions/keyboard-entry";
import { extractPixelData, rasterizeSVGCanvas } from "@graphite/utility-functions/rasterization";
Expand All @@ -34,6 +35,7 @@
let viewport: HTMLDivElement | undefined;

const editor = getContext<Editor>("editor");
const appWindow = getContext<AppWindowState>("appWindow");
const document = getContext<DocumentState>("document");

// Interactive text editing
Expand Down Expand Up @@ -514,13 +516,13 @@
<RulerInput origin={rulerOrigin.x} majorMarkSpacing={rulerSpacing} numberInterval={rulerInterval} direction="Horizontal" bind:this={rulerHorizontal} />
</LayoutRow>
{/if}
<LayoutRow class="viewport-container-inner">
<LayoutRow class="viewport-container-inner-1">
{#if rulersVisible}
<LayoutCol class="ruler-or-scrollbar">
<RulerInput origin={rulerOrigin.y} majorMarkSpacing={rulerSpacing} numberInterval={rulerInterval} direction="Vertical" bind:this={rulerVertical} />
</LayoutCol>
{/if}
<LayoutCol class="viewport-container-inner" styles={{ cursor: canvasCursor }}>
<LayoutCol class="viewport-container-inner-2" styles={{ cursor: canvasCursor }} data-viewport-container>
{#if cursorEyedropper}
<EyedropperPreview
colorChoice={cursorEyedropperPreviewColorChoice}
Expand All @@ -531,25 +533,27 @@
y={cursorTop}
/>
{/if}
<div class="viewport" on:pointerdown={(e) => canvasPointerDown(e)} bind:this={viewport} data-viewport>
<svg class="artboards" style:width={canvasWidthCSS} style:height={canvasHeightCSS}>
{@html artworkSvg}
</svg>
<div class="text-input" style:width={canvasWidthCSS} style:height={canvasHeightCSS} style:pointer-events={showTextInput ? "auto" : ""}>
{#if showTextInput}
<div bind:this={textInput} style:transform="matrix({textInputMatrix})" on:scroll={preventTextEditingScroll} />
{/if}
{#if !$appWindow.viewportHolePunch}
<div class="viewport" on:pointerdown={(e) => canvasPointerDown(e)} bind:this={viewport} data-viewport>
<svg class="artboards" style:width={canvasWidthCSS} style:height={canvasHeightCSS}>
{@html artworkSvg}
</svg>
<div class="text-input" style:width={canvasWidthCSS} style:height={canvasHeightCSS} style:pointer-events={showTextInput ? "auto" : ""}>
{#if showTextInput}
<div bind:this={textInput} style:transform="matrix({textInputMatrix})" on:scroll={preventTextEditingScroll} />
{/if}
</div>
<canvas
class="overlays"
width={canvasWidthScaledRoundedToEven}
height={canvasHeightScaledRoundedToEven}
style:width={canvasWidthCSS}
style:height={canvasHeightCSS}
data-overlays-canvas
>
</canvas>
</div>
<canvas
class="overlays"
width={canvasWidthScaledRoundedToEven}
height={canvasHeightScaledRoundedToEven}
style:width={canvasWidthCSS}
style:height={canvasHeightCSS}
data-overlays-canvas
>
</canvas>
</div>
{/if}
<div class="graph-view" class:open={$document.graphViewOverlayOpen} style:--fade-artwork={`${$document.fadeArtwork}%`} data-graph>
<Graph />
</div>
Expand Down Expand Up @@ -593,7 +597,8 @@
.control-bar {
height: 32px;
flex: 0 0 auto;
margin: 0 4px;
padding: 0 4px; // Padding (instead of margin) is needed for the viewport hole punch on desktop
background: var(--color-3-darkgray); // Needed for the viewport hole punch on desktop

.spacer {
min-width: 40px;
Expand Down Expand Up @@ -632,6 +637,7 @@
.tool-shelf {
flex: 0 0 auto;
justify-content: space-between;
background: var(--color-3-darkgray); // Needed for the viewport hole punch on desktop

.tools {
flex: 0 1 auto;
Expand Down Expand Up @@ -713,6 +719,7 @@

.ruler-or-scrollbar {
flex: 0 0 auto;
background: var(--color-3-darkgray); // Needed for the viewport hole punch on desktop
}

.ruler-corner {
Expand Down Expand Up @@ -743,7 +750,8 @@
margin-right: 16px;
}

.viewport-container-inner {
.viewport-container-inner-1,
.viewport-container-inner-2 {
flex: 1 1 100%;
position: relative;

Expand Down
13 changes: 6 additions & 7 deletions frontend/src/components/window/MainWindow.svelte
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
<script lang="ts" context="module">
export type ApplicationPlatform = "Windows" | "Mac" | "Linux" | "Web";
</script>

<script lang="ts">
import type { AppWindowPlatform } from "@graphite/messages";

import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
import StatusBar from "@graphite/components/window/status-bar/StatusBar.svelte";
import TitleBar from "@graphite/components/window/title-bar/TitleBar.svelte";
import Workspace from "@graphite/components/window/workspace/Workspace.svelte";

let platform: ApplicationPlatform = "Web";
let maximized: true;
export let platform: AppWindowPlatform;
export let maximized: boolean;
export let viewportHolePunch: boolean;
</script>

<LayoutCol class="main-window">
<LayoutCol class="main-window" classes={{ "viewport-hole-punch": viewportHolePunch }}>
<TitleBar {platform} {maximized} />

<Workspace />
Expand Down
10 changes: 3 additions & 7 deletions frontend/src/components/window/title-bar/TitleBar.svelte
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
<script lang="ts" context="module">
export type Platform = "Windows" | "Mac" | "Linux" | "Web";
</script>

<script lang="ts">
import { getContext, onMount } from "svelte";

import type { Editor } from "@graphite/editor";
import { type KeyRaw, type LayoutKeysGroup, type MenuBarEntry, type MenuListEntry, UpdateMenuBarLayout } from "@graphite/messages";
import { type KeyRaw, type LayoutKeysGroup, type MenuBarEntry, type MenuListEntry, type AppWindowPlatform, UpdateMenuBarLayout } from "@graphite/messages";
import type { PortfolioState } from "@graphite/state-providers/portfolio";
import { platformIsMac } from "@graphite/utility-functions/platform";

Expand All @@ -17,7 +13,7 @@
import WindowButtonsWindows from "@graphite/components/window/title-bar/WindowButtonsWindows.svelte";
import WindowTitle from "@graphite/components/window/title-bar/WindowTitle.svelte";

export let platform: Platform;
export let platform: AppWindowPlatform;
export let maximized: boolean;

const editor = getContext<Editor>("editor");
Expand Down Expand Up @@ -73,7 +69,7 @@
<!-- Menu bar (or on Mac: window buttons) -->
<LayoutRow class="left">
{#if platform === "Mac"}
<WindowButtonsMac {maximized} />
<WindowButtonsMac />
{:else}
{#each entries as entry}
<TextButton label={entry.label} icon={entry.icon} menuListChildren={entry.children} action={entry.action} flush={true} />
Expand Down
Loading
Loading