Desktop: Add the transparent viewport hole punch and hook up window button plumbing (#2949)
This commit is contained in:
parent
d9de1a1c73
commit
66cd7a3b76
|
|
@ -14,6 +14,7 @@ pub struct Dispatcher {
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct DispatcherMessageHandlers {
|
pub struct DispatcherMessageHandlers {
|
||||||
animation_message_handler: AnimationMessageHandler,
|
animation_message_handler: AnimationMessageHandler,
|
||||||
|
app_window_message_handler: AppWindowMessageHandler,
|
||||||
broadcast_message_handler: BroadcastMessageHandler,
|
broadcast_message_handler: BroadcastMessageHandler,
|
||||||
debug_message_handler: DebugMessageHandler,
|
debug_message_handler: DebugMessageHandler,
|
||||||
dialog_message_handler: DialogMessageHandler,
|
dialog_message_handler: DialogMessageHandler,
|
||||||
|
|
@ -129,6 +130,9 @@ impl Dispatcher {
|
||||||
Message::Animation(message) => {
|
Message::Animation(message) => {
|
||||||
self.message_handlers.animation_message_handler.process_message(message, &mut queue, ());
|
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::Broadcast(message) => self.message_handlers.broadcast_message_handler.process_message(message, &mut queue, ()),
|
||||||
Message::Debug(message) => {
|
Message::Debug(message) => {
|
||||||
self.message_handlers.debug_message_handler.process_message(message, &mut queue, ());
|
self.message_handlers.debug_message_handler.process_message(message, &mut queue, ());
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use super::utility_types::{FrontendDocumentDetails, MouseCursorIcon};
|
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::layout::utility_types::widget_prelude::*;
|
||||||
use crate::messages::portfolio::document::node_graph::utility_types::{
|
use crate::messages::portfolio::document::node_graph::utility_types::{
|
||||||
BoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeType, Transform,
|
BoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeType, Transform,
|
||||||
|
|
@ -309,4 +310,13 @@ pub enum FrontendMessage {
|
||||||
layout_target: LayoutTarget,
|
layout_target: LayoutTarget,
|
||||||
diff: Vec<WidgetDiff>,
|
diff: Vec<WidgetDiff>,
|
||||||
},
|
},
|
||||||
|
UpdatePlatform {
|
||||||
|
platform: AppWindowPlatform,
|
||||||
|
},
|
||||||
|
UpdateMaximized {
|
||||||
|
maximized: bool,
|
||||||
|
},
|
||||||
|
UpdateViewportHolePunch {
|
||||||
|
active: bool,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ pub enum Message {
|
||||||
#[child]
|
#[child]
|
||||||
Animation(AnimationMessage),
|
Animation(AnimationMessage),
|
||||||
#[child]
|
#[child]
|
||||||
|
AppWindow(AppWindowMessage),
|
||||||
|
#[child]
|
||||||
Broadcast(BroadcastMessage),
|
Broadcast(BroadcastMessage),
|
||||||
#[child]
|
#[child]
|
||||||
Debug(DebugMessage),
|
Debug(DebugMessage),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
//! The root-level messages forming the first layer of the message system architecture.
|
//! The root-level messages forming the first layer of the message system architecture.
|
||||||
|
|
||||||
pub mod animation;
|
pub mod animation;
|
||||||
|
pub mod app_window;
|
||||||
pub mod broadcast;
|
pub mod broadcast;
|
||||||
pub mod debug;
|
pub mod debug;
|
||||||
pub mod dialog;
|
pub mod dialog;
|
||||||
|
|
|
||||||
|
|
@ -107,15 +107,15 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||||
|
|
||||||
let compatible_type = first_layer.and_then(|layer| {
|
let compatible_type = first_layer.and_then(|layer| {
|
||||||
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &document.network_interface);
|
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, &[]);
|
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_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);
|
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &document.network_interface);
|
||||||
matches!(graph_layer.find_input("Path", 1), Some(TaggedValue::VectorModification(_)))
|
matches!(graph_layer.find_input("Path", 1), Some(TaggedValue::VectorModification(_)))
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ impl From<String> for PanelType {
|
||||||
"Layers" => PanelType::Layers,
|
"Layers" => PanelType::Layers,
|
||||||
"Properties" => PanelType::Properties,
|
"Properties" => PanelType::Properties,
|
||||||
"Spreadsheet" => PanelType::Spreadsheet,
|
"Spreadsheet" => PanelType::Spreadsheet,
|
||||||
_ => panic!("Unknown panel type: {}", value),
|
_ => panic!("Unknown panel type: {value}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ pub use crate::utility_traits::{ActionList, AsMessage, HierarchicalTree, Message
|
||||||
pub use crate::utility_types::{DebugMessageTree, MessageData};
|
pub use crate::utility_types::{DebugMessageTree, MessageData};
|
||||||
// Message, MessageData, MessageDiscriminant, MessageHandler
|
// Message, MessageData, MessageDiscriminant, MessageHandler
|
||||||
pub use crate::messages::animation::{AnimationMessage, AnimationMessageDiscriminant, AnimationMessageHandler};
|
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::broadcast::{BroadcastMessage, BroadcastMessageDiscriminant, BroadcastMessageHandler};
|
||||||
pub use crate::messages::debug::{DebugMessage, DebugMessageDiscriminant, DebugMessageHandler};
|
pub use crate::messages::debug::{DebugMessage, DebugMessageDiscriminant, DebugMessageHandler};
|
||||||
pub use crate::messages::dialog::export_dialog::{ExportDialogMessage, ExportDialogMessageContext, ExportDialogMessageDiscriminant, ExportDialogMessageHandler};
|
pub use crate::messages::dialog::export_dialog::{ExportDialogMessage, ExportDialogMessageContext, ExportDialogMessageDiscriminant, ExportDialogMessageHandler};
|
||||||
|
|
|
||||||
|
|
@ -21,11 +21,17 @@
|
||||||
<noscript>JavaScript is required</noscript>
|
<noscript>JavaScript is required</noscript>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
background: #222;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: #222;
|
||||||
|
}
|
||||||
|
|
||||||
body::after {
|
body::after {
|
||||||
content: "";
|
content: "";
|
||||||
display: block;
|
display: block;
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
import { createLocalizationManager } from "@graphite/io-managers/localization";
|
import { createLocalizationManager } from "@graphite/io-managers/localization";
|
||||||
import { createPanicManager } from "@graphite/io-managers/panic";
|
import { createPanicManager } from "@graphite/io-managers/panic";
|
||||||
import { createPersistenceManager } from "@graphite/io-managers/persistence";
|
import { createPersistenceManager } from "@graphite/io-managers/persistence";
|
||||||
|
import { createAppWindowState } from "@graphite/state-providers/app-window";
|
||||||
import { createDialogState } from "@graphite/state-providers/dialog";
|
import { createDialogState } from "@graphite/state-providers/dialog";
|
||||||
import { createDocumentState } from "@graphite/state-providers/document";
|
import { createDocumentState } from "@graphite/state-providers/document";
|
||||||
import { createFontsState } from "@graphite/state-providers/fonts";
|
import { createFontsState } from "@graphite/state-providers/fonts";
|
||||||
|
|
@ -36,6 +37,8 @@
|
||||||
setContext("nodeGraph", nodeGraph);
|
setContext("nodeGraph", nodeGraph);
|
||||||
let portfolio = createPortfolioState(editor);
|
let portfolio = createPortfolioState(editor);
|
||||||
setContext("portfolio", portfolio);
|
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.)
|
// 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);
|
createClipboardManager(editor);
|
||||||
|
|
@ -58,10 +61,11 @@
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<MainWindow />
|
<MainWindow platform={$appWindow.platform} maximized={$appWindow.maximized} viewportHolePunch={$appWindow.viewportHolePunch} />
|
||||||
|
|
||||||
<style lang="scss" global>
|
<style lang="scss" global>
|
||||||
// Disable the spinning loading indicator
|
// Disable the spinning loading indicator
|
||||||
|
body::before,
|
||||||
body::after {
|
body::after {
|
||||||
content: none !important;
|
content: none !important;
|
||||||
}
|
}
|
||||||
|
|
@ -206,10 +210,16 @@
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: var(--color-2-mildblack);
|
background: var(--color-2-mildblack);
|
||||||
overscroll-behavior: none;
|
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;
|
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:
|
// 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
|
// https://stackoverflow.com/questions/36247140/why-dont-flex-items-shrink-past-content-size
|
||||||
* {
|
* {
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
UpdateMouseCursor,
|
UpdateMouseCursor,
|
||||||
isWidgetSpanRow,
|
isWidgetSpanRow,
|
||||||
} from "@graphite/messages";
|
} from "@graphite/messages";
|
||||||
|
import type { AppWindowState } from "@graphite/state-providers/app-window";
|
||||||
import type { DocumentState } from "@graphite/state-providers/document";
|
import type { DocumentState } from "@graphite/state-providers/document";
|
||||||
import { textInputCleanup } from "@graphite/utility-functions/keyboard-entry";
|
import { textInputCleanup } from "@graphite/utility-functions/keyboard-entry";
|
||||||
import { extractPixelData, rasterizeSVGCanvas } from "@graphite/utility-functions/rasterization";
|
import { extractPixelData, rasterizeSVGCanvas } from "@graphite/utility-functions/rasterization";
|
||||||
|
|
@ -34,6 +35,7 @@
|
||||||
let viewport: HTMLDivElement | undefined;
|
let viewport: HTMLDivElement | undefined;
|
||||||
|
|
||||||
const editor = getContext<Editor>("editor");
|
const editor = getContext<Editor>("editor");
|
||||||
|
const appWindow = getContext<AppWindowState>("appWindow");
|
||||||
const document = getContext<DocumentState>("document");
|
const document = getContext<DocumentState>("document");
|
||||||
|
|
||||||
// Interactive text editing
|
// Interactive text editing
|
||||||
|
|
@ -514,13 +516,13 @@
|
||||||
<RulerInput origin={rulerOrigin.x} majorMarkSpacing={rulerSpacing} numberInterval={rulerInterval} direction="Horizontal" bind:this={rulerHorizontal} />
|
<RulerInput origin={rulerOrigin.x} majorMarkSpacing={rulerSpacing} numberInterval={rulerInterval} direction="Horizontal" bind:this={rulerHorizontal} />
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
{/if}
|
{/if}
|
||||||
<LayoutRow class="viewport-container-inner">
|
<LayoutRow class="viewport-container-inner-1">
|
||||||
{#if rulersVisible}
|
{#if rulersVisible}
|
||||||
<LayoutCol class="ruler-or-scrollbar">
|
<LayoutCol class="ruler-or-scrollbar">
|
||||||
<RulerInput origin={rulerOrigin.y} majorMarkSpacing={rulerSpacing} numberInterval={rulerInterval} direction="Vertical" bind:this={rulerVertical} />
|
<RulerInput origin={rulerOrigin.y} majorMarkSpacing={rulerSpacing} numberInterval={rulerInterval} direction="Vertical" bind:this={rulerVertical} />
|
||||||
</LayoutCol>
|
</LayoutCol>
|
||||||
{/if}
|
{/if}
|
||||||
<LayoutCol class="viewport-container-inner" styles={{ cursor: canvasCursor }}>
|
<LayoutCol class="viewport-container-inner-2" styles={{ cursor: canvasCursor }} data-viewport-container>
|
||||||
{#if cursorEyedropper}
|
{#if cursorEyedropper}
|
||||||
<EyedropperPreview
|
<EyedropperPreview
|
||||||
colorChoice={cursorEyedropperPreviewColorChoice}
|
colorChoice={cursorEyedropperPreviewColorChoice}
|
||||||
|
|
@ -531,25 +533,27 @@
|
||||||
y={cursorTop}
|
y={cursorTop}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="viewport" on:pointerdown={(e) => canvasPointerDown(e)} bind:this={viewport} data-viewport>
|
{#if !$appWindow.viewportHolePunch}
|
||||||
<svg class="artboards" style:width={canvasWidthCSS} style:height={canvasHeightCSS}>
|
<div class="viewport" on:pointerdown={(e) => canvasPointerDown(e)} bind:this={viewport} data-viewport>
|
||||||
{@html artworkSvg}
|
<svg class="artboards" style:width={canvasWidthCSS} style:height={canvasHeightCSS}>
|
||||||
</svg>
|
{@html artworkSvg}
|
||||||
<div class="text-input" style:width={canvasWidthCSS} style:height={canvasHeightCSS} style:pointer-events={showTextInput ? "auto" : ""}>
|
</svg>
|
||||||
{#if showTextInput}
|
<div class="text-input" style:width={canvasWidthCSS} style:height={canvasHeightCSS} style:pointer-events={showTextInput ? "auto" : ""}>
|
||||||
<div bind:this={textInput} style:transform="matrix({textInputMatrix})" on:scroll={preventTextEditingScroll} />
|
{#if showTextInput}
|
||||||
{/if}
|
<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>
|
</div>
|
||||||
<canvas
|
{/if}
|
||||||
class="overlays"
|
|
||||||
width={canvasWidthScaledRoundedToEven}
|
|
||||||
height={canvasHeightScaledRoundedToEven}
|
|
||||||
style:width={canvasWidthCSS}
|
|
||||||
style:height={canvasHeightCSS}
|
|
||||||
data-overlays-canvas
|
|
||||||
>
|
|
||||||
</canvas>
|
|
||||||
</div>
|
|
||||||
<div class="graph-view" class:open={$document.graphViewOverlayOpen} style:--fade-artwork={`${$document.fadeArtwork}%`} data-graph>
|
<div class="graph-view" class:open={$document.graphViewOverlayOpen} style:--fade-artwork={`${$document.fadeArtwork}%`} data-graph>
|
||||||
<Graph />
|
<Graph />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -593,7 +597,8 @@
|
||||||
.control-bar {
|
.control-bar {
|
||||||
height: 32px;
|
height: 32px;
|
||||||
flex: 0 0 auto;
|
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 {
|
.spacer {
|
||||||
min-width: 40px;
|
min-width: 40px;
|
||||||
|
|
@ -632,6 +637,7 @@
|
||||||
.tool-shelf {
|
.tool-shelf {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
background: var(--color-3-darkgray); // Needed for the viewport hole punch on desktop
|
||||||
|
|
||||||
.tools {
|
.tools {
|
||||||
flex: 0 1 auto;
|
flex: 0 1 auto;
|
||||||
|
|
@ -713,6 +719,7 @@
|
||||||
|
|
||||||
.ruler-or-scrollbar {
|
.ruler-or-scrollbar {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
background: var(--color-3-darkgray); // Needed for the viewport hole punch on desktop
|
||||||
}
|
}
|
||||||
|
|
||||||
.ruler-corner {
|
.ruler-corner {
|
||||||
|
|
@ -743,7 +750,8 @@
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.viewport-container-inner {
|
.viewport-container-inner-1,
|
||||||
|
.viewport-container-inner-2 {
|
||||||
flex: 1 1 100%;
|
flex: 1 1 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,17 @@
|
||||||
<script lang="ts" context="module">
|
|
||||||
export type ApplicationPlatform = "Windows" | "Mac" | "Linux" | "Web";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import type { AppWindowPlatform } from "@graphite/messages";
|
||||||
|
|
||||||
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||||
import StatusBar from "@graphite/components/window/status-bar/StatusBar.svelte";
|
import StatusBar from "@graphite/components/window/status-bar/StatusBar.svelte";
|
||||||
import TitleBar from "@graphite/components/window/title-bar/TitleBar.svelte";
|
import TitleBar from "@graphite/components/window/title-bar/TitleBar.svelte";
|
||||||
import Workspace from "@graphite/components/window/workspace/Workspace.svelte";
|
import Workspace from "@graphite/components/window/workspace/Workspace.svelte";
|
||||||
|
|
||||||
let platform: ApplicationPlatform = "Web";
|
export let platform: AppWindowPlatform;
|
||||||
let maximized: true;
|
export let maximized: boolean;
|
||||||
|
export let viewportHolePunch: boolean;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<LayoutCol class="main-window">
|
<LayoutCol class="main-window" classes={{ "viewport-hole-punch": viewportHolePunch }}>
|
||||||
<TitleBar {platform} {maximized} />
|
<TitleBar {platform} {maximized} />
|
||||||
|
|
||||||
<Workspace />
|
<Workspace />
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,8 @@
|
||||||
<script lang="ts" context="module">
|
|
||||||
export type Platform = "Windows" | "Mac" | "Linux" | "Web";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext, onMount } from "svelte";
|
import { getContext, onMount } from "svelte";
|
||||||
|
|
||||||
import type { Editor } from "@graphite/editor";
|
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 type { PortfolioState } from "@graphite/state-providers/portfolio";
|
||||||
import { platformIsMac } from "@graphite/utility-functions/platform";
|
import { platformIsMac } from "@graphite/utility-functions/platform";
|
||||||
|
|
||||||
|
|
@ -17,7 +13,7 @@
|
||||||
import WindowButtonsWindows from "@graphite/components/window/title-bar/WindowButtonsWindows.svelte";
|
import WindowButtonsWindows from "@graphite/components/window/title-bar/WindowButtonsWindows.svelte";
|
||||||
import WindowTitle from "@graphite/components/window/title-bar/WindowTitle.svelte";
|
import WindowTitle from "@graphite/components/window/title-bar/WindowTitle.svelte";
|
||||||
|
|
||||||
export let platform: Platform;
|
export let platform: AppWindowPlatform;
|
||||||
export let maximized: boolean;
|
export let maximized: boolean;
|
||||||
|
|
||||||
const editor = getContext<Editor>("editor");
|
const editor = getContext<Editor>("editor");
|
||||||
|
|
@ -73,7 +69,7 @@
|
||||||
<!-- Menu bar (or on Mac: window buttons) -->
|
<!-- Menu bar (or on Mac: window buttons) -->
|
||||||
<LayoutRow class="left">
|
<LayoutRow class="left">
|
||||||
{#if platform === "Mac"}
|
{#if platform === "Mac"}
|
||||||
<WindowButtonsMac {maximized} />
|
<WindowButtonsMac />
|
||||||
{:else}
|
{:else}
|
||||||
{#each entries as entry}
|
{#each entries as entry}
|
||||||
<TextButton label={entry.label} icon={entry.icon} menuListChildren={entry.children} action={entry.action} flush={true} />
|
<TextButton label={entry.label} icon={entry.icon} menuListChildren={entry.children} action={entry.action} flush={true} />
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,17 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { getContext } from "svelte";
|
||||||
|
|
||||||
|
import type { Editor } from "@graphite/editor";
|
||||||
|
|
||||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||||
|
|
||||||
export let maximized = false;
|
const editor = getContext<Editor>("editor");
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<LayoutRow class="window-buttons-mac">
|
<LayoutRow class="window-buttons-mac">
|
||||||
<div class="close" title="Close" />
|
<div class="close" on:click={() => editor.handle.appWindowClose()} />
|
||||||
<div class="minimize" title={maximized ? "Minimize" : "Maximize"} />
|
<div class="minimize" on:click={() => editor.handle.appWindowMinimize()} />
|
||||||
<div class="zoom" title="Zoom" />
|
<div class="zoom" on:click={() => editor.handle.appWindowMaximize()} />
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
|
|
||||||
<style lang="scss" global>
|
<style lang="scss" global>
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,32 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext } from "svelte";
|
import { getContext } from "svelte";
|
||||||
|
|
||||||
|
import type { Editor } from "@graphite/editor";
|
||||||
import type { FullscreenState } from "@graphite/state-providers/fullscreen";
|
import type { FullscreenState } from "@graphite/state-providers/fullscreen";
|
||||||
|
|
||||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||||
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
|
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
|
||||||
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
|
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
|
||||||
|
|
||||||
|
const editor = getContext<Editor>("editor");
|
||||||
const fullscreen = getContext<FullscreenState>("fullscreen");
|
const fullscreen = getContext<FullscreenState>("fullscreen");
|
||||||
|
|
||||||
$: requestFullscreenHotkeys = fullscreen.keyboardLockApiSupported && !$fullscreen.keyboardLocked;
|
$: requestFullscreenHotkeys = fullscreen.keyboardLockApiSupported && !$fullscreen.keyboardLocked;
|
||||||
|
|
||||||
async function handleClick() {
|
async function handleClick(e: MouseEvent) {
|
||||||
|
// TODO: Remove this debugging option to switch from web to desktop window buttons
|
||||||
|
if (e.ctrlKey && e.shiftKey && e.altKey) {
|
||||||
|
editor.handle.appWindowMinimize();
|
||||||
|
editor.handle.appWindowMinimize();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if ($fullscreen.windowFullscreen) fullscreen.exitFullscreen();
|
if ($fullscreen.windowFullscreen) fullscreen.exitFullscreen();
|
||||||
else fullscreen.enterFullscreen();
|
else fullscreen.enterFullscreen();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<LayoutRow class="window-buttons-web" on:click={() => handleClick()} tooltip={($fullscreen.windowFullscreen ? "Exit" : "Enter") + " Fullscreen (F11)"}>
|
<LayoutRow class="window-buttons-web" on:click={handleClick} tooltip={$fullscreen.windowFullscreen ? "Exit Fullscreen (F11)" : "Enter Fullscreen (F11)"}>
|
||||||
{#if requestFullscreenHotkeys}
|
{#if requestFullscreenHotkeys}
|
||||||
<TextLabel italic={true}>Go fullscreen to access all hotkeys</TextLabel>
|
<TextLabel italic={true}>Go fullscreen to access all hotkeys</TextLabel>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,23 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { getContext } from "svelte";
|
||||||
|
|
||||||
|
import type { Editor } from "@graphite/editor";
|
||||||
|
|
||||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||||
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
|
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
|
||||||
|
|
||||||
export let maximized = false;
|
export let maximized;
|
||||||
|
|
||||||
|
const editor = getContext<Editor>("editor");
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<LayoutRow class="window-button windows minimize" tooltip="Minimize">
|
<LayoutRow class="window-button windows" tooltip="Minimize" on:click={() => editor.handle.appWindowMinimize()}>
|
||||||
<IconLabel icon={"WindowButtonWinMinimize"} />
|
<IconLabel icon={"WindowButtonWinMinimize"} />
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
{#if !maximized}
|
<LayoutRow class="window-button windows" tooltip={maximized ? "Restore Down" : "Maximize"} on:click={() => editor.handle.appWindowMaximize()}>
|
||||||
<LayoutRow class="window-button windows maximize" tooltip="Maximize">
|
<IconLabel icon={maximized ? "WindowButtonWinRestoreDown" : "WindowButtonWinMaximize"} />
|
||||||
<IconLabel icon={"WindowButtonWinMaximize"} />
|
</LayoutRow>
|
||||||
</LayoutRow>
|
<LayoutRow class="window-button windows" tooltip="Close" on:click={() => editor.handle.appWindowClose()}>
|
||||||
{:else}
|
|
||||||
<LayoutRow class="window-button windows restore-down" tooltip="Restore Down">
|
|
||||||
<IconLabel icon={"WindowButtonWinRestoreDown"} />
|
|
||||||
</LayoutRow>
|
|
||||||
{/if}
|
|
||||||
<LayoutRow class="window-button windows close" tooltip="Close">
|
|
||||||
<IconLabel icon={"WindowButtonWinClose"} />
|
<IconLabel icon={"WindowButtonWinClose"} />
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
|
|
||||||
|
|
@ -39,7 +39,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.close:hover {
|
&:last-of-type:hover {
|
||||||
background: #e81123;
|
background: #e81123;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,13 @@
|
||||||
export let clickAction: ((index: number) => void) | undefined = undefined;
|
export let clickAction: ((index: number) => void) | undefined = undefined;
|
||||||
export let closeAction: ((index: number) => void) | undefined = undefined;
|
export let closeAction: ((index: number) => void) | undefined = undefined;
|
||||||
|
|
||||||
|
let className = "";
|
||||||
|
export { className as class };
|
||||||
|
export let classes: Record<string, boolean> = {};
|
||||||
|
let styleName = "";
|
||||||
|
export { styleName as style };
|
||||||
|
export let styles: Record<string, string | number | undefined> = {};
|
||||||
|
|
||||||
let tabElements: (LayoutRow | undefined)[] = [];
|
let tabElements: (LayoutRow | undefined)[] = [];
|
||||||
|
|
||||||
function platformModifiers(reservedKey: boolean): LayoutKeysGroup {
|
function platformModifiers(reservedKey: boolean): LayoutKeysGroup {
|
||||||
|
|
@ -90,7 +97,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<LayoutCol class="panel" on:pointerdown={() => panelType && editor.handle.setActivePanel(panelType)}>
|
<LayoutCol on:pointerdown={() => panelType && editor.handle.setActivePanel(panelType)} class={`panel ${className}`.trim()} {classes} style={styleName} {styles}>
|
||||||
<LayoutRow class="tab-bar" classes={{ "min-widths": tabMinWidths }}>
|
<LayoutRow class="tab-bar" classes={{ "min-widths": tabMinWidths }}>
|
||||||
<LayoutRow class="tab-group" scrollableX={true}>
|
<LayoutRow class="tab-group" scrollableX={true}>
|
||||||
{#each tabLabels as tabLabel, tabIndex}
|
{#each tabLabels as tabLabel, tabIndex}
|
||||||
|
|
@ -194,6 +201,7 @@
|
||||||
.tab-bar {
|
.tab-bar {
|
||||||
height: 28px;
|
height: 28px;
|
||||||
min-height: auto;
|
min-height: auto;
|
||||||
|
background: var(--color-1-nearblack); // Needed for the viewport hole punch on desktop
|
||||||
|
|
||||||
&.min-widths .tab-group .tab {
|
&.min-widths .tab-group .tab {
|
||||||
min-width: 120px;
|
min-width: 120px;
|
||||||
|
|
@ -336,5 +344,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Needed for the viewport hole punch on desktop
|
||||||
|
.viewport-hole-punch &.document-panel,
|
||||||
|
.viewport-hole-punch &.document-panel .panel-body:not(:has(.empty-panel)) {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -137,6 +137,7 @@
|
||||||
<LayoutCol class="workspace-grid-subdivision" styles={{ "flex-grow": panelSizes["content"] }} data-subdivision-name="content">
|
<LayoutCol class="workspace-grid-subdivision" styles={{ "flex-grow": panelSizes["content"] }} data-subdivision-name="content">
|
||||||
<LayoutRow class="workspace-grid-subdivision" styles={{ "flex-grow": panelSizes["document"] }} data-subdivision-name="document">
|
<LayoutRow class="workspace-grid-subdivision" styles={{ "flex-grow": panelSizes["document"] }} data-subdivision-name="document">
|
||||||
<Panel
|
<Panel
|
||||||
|
class="document-panel"
|
||||||
panelType={$portfolio.documents.length > 0 ? "Document" : undefined}
|
panelType={$portfolio.documents.length > 0 ? "Document" : undefined}
|
||||||
tabCloseButtons={true}
|
tabCloseButtons={true}
|
||||||
tabMinWidths={true}
|
tabMinWidths={true}
|
||||||
|
|
@ -176,8 +177,9 @@
|
||||||
flex: 1 1 100%;
|
flex: 1 1 100%;
|
||||||
|
|
||||||
.workspace-grid-subdivision {
|
.workspace-grid-subdivision {
|
||||||
min-height: 28px;
|
position: relative;
|
||||||
flex: 1 1 0;
|
flex: 1 1 0;
|
||||||
|
min-height: 28px;
|
||||||
|
|
||||||
&.folded {
|
&.folded {
|
||||||
flex-grow: 0;
|
flex-grow: 0;
|
||||||
|
|
@ -196,5 +198,15 @@
|
||||||
cursor: ew-resize;
|
cursor: ew-resize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Needed for the viewport hole punch on desktop
|
||||||
|
.viewport-hole-punch & .workspace-grid-subdivision:has(.panel.document-panel)::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 6px;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0 0 0 calc(100vw + 100vh) var(--color-2-mildblack);
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -169,7 +169,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
||||||
potentiallyRestoreCanvasFocus(e);
|
potentiallyRestoreCanvasFocus(e);
|
||||||
|
|
||||||
const { target } = e;
|
const { target } = e;
|
||||||
const isTargetingCanvas = target instanceof Element && target.closest("[data-viewport], [data-node-graph]");
|
const isTargetingCanvas = target instanceof Element && target.closest("[data-viewport], [data-viewport-container], [data-node-graph]");
|
||||||
const inDialog = target instanceof Element && target.closest("[data-dialog] [data-floating-menu-content]");
|
const inDialog = target instanceof Element && target.closest("[data-dialog] [data-floating-menu-content]");
|
||||||
const inContextMenu = target instanceof Element && target.closest("[data-context-menu]");
|
const inContextMenu = target instanceof Element && target.closest("[data-context-menu]");
|
||||||
const inTextInput = target === textToolInteractiveInputElement;
|
const inTextInput = target === textToolInteractiveInputElement;
|
||||||
|
|
@ -219,7 +219,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
||||||
|
|
||||||
// Allow only events within the viewport or node graph boundaries
|
// Allow only events within the viewport or node graph boundaries
|
||||||
const { target } = e;
|
const { target } = e;
|
||||||
const isTargetingCanvas = target instanceof Element && target.closest("[data-viewport], [data-node-graph]");
|
const isTargetingCanvas = target instanceof Element && target.closest("[data-viewport], [data-viewport-container], [data-node-graph]");
|
||||||
if (!(isTargetingCanvas instanceof Element)) return;
|
if (!(isTargetingCanvas instanceof Element)) return;
|
||||||
|
|
||||||
// Allow only repeated increments of double-clicks (not 1, 3, 5, etc.)
|
// Allow only repeated increments of double-clicks (not 1, 3, 5, etc.)
|
||||||
|
|
@ -256,7 +256,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
||||||
|
|
||||||
function onWheelScroll(e: WheelEvent) {
|
function onWheelScroll(e: WheelEvent) {
|
||||||
const { target } = e;
|
const { target } = e;
|
||||||
const isTargetingCanvas = target instanceof Element && target.closest("[data-viewport], [data-node-graph]");
|
const isTargetingCanvas = target instanceof Element && target.closest("[data-viewport], [data-viewport-container], [data-node-graph]");
|
||||||
|
|
||||||
// Redirect vertical scroll wheel movement into a horizontal scroll on a horizontally scrollable element
|
// Redirect vertical scroll wheel movement into a horizontal scroll on a horizontally scrollable element
|
||||||
// There seems to be no possible way to properly employ the browser's smooth scrolling interpolation
|
// There seems to be no possible way to properly employ the browser's smooth scrolling interpolation
|
||||||
|
|
@ -502,7 +502,9 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
||||||
|
|
||||||
function potentiallyRestoreCanvasFocus(e: Event) {
|
function potentiallyRestoreCanvasFocus(e: Event) {
|
||||||
const { target } = e;
|
const { target } = e;
|
||||||
const newInCanvasArea = (target instanceof Element && target.closest("[data-viewport], [data-graph]")) instanceof Element && !targetIsTextField(window.document.activeElement || undefined);
|
const newInCanvasArea =
|
||||||
|
(target instanceof Element && target.closest("[data-viewport], [data-viewport-container], [data-graph]")) instanceof Element &&
|
||||||
|
!targetIsTextField(window.document.activeElement || undefined);
|
||||||
if (!canvasFocused && newInCanvasArea) {
|
if (!canvasFocused && newInCanvasArea) {
|
||||||
canvasFocused = true;
|
canvasFocused = true;
|
||||||
app?.focus();
|
app?.focus();
|
||||||
|
|
|
||||||
|
|
@ -349,6 +349,21 @@ export class TriggerIndexedDbRemoveDocument extends JsMessage {
|
||||||
documentId!: string;
|
documentId!: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type AppWindowPlatform = "Web" | "Windows" | "Mac" | "Linux";
|
||||||
|
|
||||||
|
export class UpdatePlatform extends JsMessage {
|
||||||
|
@Transform(({ value }: { value: AppWindowPlatform }) => value)
|
||||||
|
readonly platform!: AppWindowPlatform;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UpdateMaximized extends JsMessage {
|
||||||
|
readonly maximized!: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UpdateViewportHolePunch extends JsMessage {
|
||||||
|
readonly active!: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export class UpdateInputHints extends JsMessage {
|
export class UpdateInputHints extends JsMessage {
|
||||||
@Type(() => HintInfo)
|
@Type(() => HintInfo)
|
||||||
readonly hintData!: HintData;
|
readonly hintData!: HintData;
|
||||||
|
|
@ -1670,29 +1685,32 @@ export const messageMakers: Record<string, MessageMaker> = {
|
||||||
UpdateEyedropperSamplingState,
|
UpdateEyedropperSamplingState,
|
||||||
UpdateGraphFadeArtwork,
|
UpdateGraphFadeArtwork,
|
||||||
UpdateGraphViewOverlay,
|
UpdateGraphViewOverlay,
|
||||||
UpdateSpreadsheetState,
|
|
||||||
UpdateImportReorderIndex,
|
UpdateImportReorderIndex,
|
||||||
UpdateImportsExports,
|
UpdateImportsExports,
|
||||||
UpdateInputHints,
|
UpdateInputHints,
|
||||||
UpdateInSelectedNetwork,
|
UpdateInSelectedNetwork,
|
||||||
|
UpdateLayersPanelBottomBarLayout,
|
||||||
UpdateLayersPanelControlBarLeftLayout,
|
UpdateLayersPanelControlBarLeftLayout,
|
||||||
UpdateLayersPanelControlBarRightLayout,
|
UpdateLayersPanelControlBarRightLayout,
|
||||||
UpdateLayersPanelBottomBarLayout,
|
|
||||||
UpdateLayerWidths,
|
UpdateLayerWidths,
|
||||||
|
UpdateMaximized,
|
||||||
UpdateMenuBarLayout,
|
UpdateMenuBarLayout,
|
||||||
UpdateMouseCursor,
|
UpdateMouseCursor,
|
||||||
UpdateNodeGraphNodes,
|
|
||||||
UpdateVisibleNodes,
|
|
||||||
UpdateNodeGraphWires,
|
|
||||||
UpdateNodeGraphTransform,
|
|
||||||
UpdateNodeGraphControlBarLayout,
|
UpdateNodeGraphControlBarLayout,
|
||||||
|
UpdateNodeGraphNodes,
|
||||||
UpdateNodeGraphSelection,
|
UpdateNodeGraphSelection,
|
||||||
|
UpdateNodeGraphTransform,
|
||||||
|
UpdateNodeGraphWires,
|
||||||
UpdateNodeThumbnail,
|
UpdateNodeThumbnail,
|
||||||
UpdateOpenDocumentsList,
|
UpdateOpenDocumentsList,
|
||||||
|
UpdatePlatform,
|
||||||
UpdatePropertyPanelSectionsLayout,
|
UpdatePropertyPanelSectionsLayout,
|
||||||
UpdateSpreadsheetLayout,
|
UpdateSpreadsheetLayout,
|
||||||
|
UpdateSpreadsheetState,
|
||||||
UpdateToolOptionsLayout,
|
UpdateToolOptionsLayout,
|
||||||
UpdateToolShelfLayout,
|
UpdateToolShelfLayout,
|
||||||
|
UpdateViewportHolePunch,
|
||||||
|
UpdateVisibleNodes,
|
||||||
UpdateWirePathInProgress,
|
UpdateWirePathInProgress,
|
||||||
UpdateWorkingColorsLayout,
|
UpdateWorkingColorsLayout,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
/* eslint-disable max-classes-per-file */
|
||||||
|
|
||||||
|
import { writable } from "svelte/store";
|
||||||
|
|
||||||
|
import { type Editor } from "@graphite/editor";
|
||||||
|
import { type AppWindowPlatform, UpdatePlatform, UpdateMaximized, UpdateViewportHolePunch } from "@graphite/messages";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||||
|
export function createAppWindowState(editor: Editor) {
|
||||||
|
const { subscribe, update } = writable({
|
||||||
|
platform: "Web" as AppWindowPlatform,
|
||||||
|
maximized: false,
|
||||||
|
viewportHolePunch: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set up message subscriptions on creation
|
||||||
|
editor.subscriptions.subscribeJsMessage(UpdatePlatform, (updatePlatform) => {
|
||||||
|
update((state) => {
|
||||||
|
state.platform = updatePlatform.platform;
|
||||||
|
return state;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
editor.subscriptions.subscribeJsMessage(UpdateMaximized, (maximized) => {
|
||||||
|
update((state) => {
|
||||||
|
state.maximized = maximized.maximized;
|
||||||
|
return state;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
editor.subscriptions.subscribeJsMessage(UpdateViewportHolePunch, (viewportHolePunch) => {
|
||||||
|
update((state) => {
|
||||||
|
state.viewportHolePunch = viewportHolePunch.active;
|
||||||
|
return state;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export type AppWindowState = ReturnType<typeof createAppWindowState>;
|
||||||
|
|
@ -103,7 +103,6 @@ export function createPortfolioState(editor: Editor) {
|
||||||
// Fail silently if there's an error rasterizing the SVG, such as a zero-sized image
|
// Fail silently if there's an error rasterizing the SVG, such as a zero-sized image
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeJsMessage(UpdateSpreadsheetState, async (updateSpreadsheetState) => {
|
editor.subscriptions.subscribeJsMessage(UpdateSpreadsheetState, async (updateSpreadsheetState) => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
state.spreadsheetOpen = updateSpreadsheetState.open;
|
state.spreadsheetOpen = updateSpreadsheetState.open;
|
||||||
|
|
@ -111,7 +110,6 @@ export function createPortfolioState(editor: Editor) {
|
||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeJsMessage(UpdateSpreadsheetLayout, (updateSpreadsheetLayout) => {
|
editor.subscriptions.subscribeJsMessage(UpdateSpreadsheetLayout, (updateSpreadsheetLayout) => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
patchWidgetLayout(state.spreadsheetWidgets, updateSpreadsheetLayout);
|
patchWidgetLayout(state.spreadsheetWidgets, updateSpreadsheetLayout);
|
||||||
|
|
|
||||||
|
|
@ -270,6 +270,27 @@ impl EditorHandle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Minimizes the application window to the taskbar or dock
|
||||||
|
#[wasm_bindgen(js_name = appWindowMinimize)]
|
||||||
|
pub fn app_window_minimize(&self) {
|
||||||
|
let message = AppWindowMessage::AppWindowMinimize;
|
||||||
|
self.dispatch(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Toggles minimizing or restoring down the application window
|
||||||
|
#[wasm_bindgen(js_name = appWindowMaximize)]
|
||||||
|
pub fn app_window_maximize(&self) {
|
||||||
|
let message = AppWindowMessage::AppWindowMaximize;
|
||||||
|
self.dispatch(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Closes the application window
|
||||||
|
#[wasm_bindgen(js_name = appWindowClose)]
|
||||||
|
pub fn app_window_close(&self) {
|
||||||
|
let message = AppWindowMessage::AppWindowClose;
|
||||||
|
self.dispatch(message);
|
||||||
|
}
|
||||||
|
|
||||||
/// Displays a dialog with an error message
|
/// Displays a dialog with an error message
|
||||||
#[wasm_bindgen(js_name = errorDialog)]
|
#[wasm_bindgen(js_name = errorDialog)]
|
||||||
pub fn error_dialog(&self, title: String, description: String) {
|
pub fn error_dialog(&self, title: String, description: String) {
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ impl Context {
|
||||||
trace: wgpu::Trace::Off,
|
trace: wgpu::Trace::Off,
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.ok()?;
|
||||||
|
|
||||||
let info = adapter.get_info();
|
let info = adapter.get_info();
|
||||||
// skip this on LavaPipe temporarily
|
// skip this on LavaPipe temporarily
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue