Rename and reorganize several widgets (#1462)

* Rename SwatchPairInput -> WorkingColorsButton

* Remove unnecessary Svelte each-loop keys

* Rename (and migrate) MenuBarInput -> MenuListButton

* Rename PivotAssist -> PivotInput

* Rename PersistentScrollbar -> ScrollbarInput and CanvasRuler -> RulerInput

* Rename DIalogModal -> Dialog

* Rename WidgetRow -> WidgetSpan
This commit is contained in:
Keavon Chambers 2023-11-18 04:34:30 -08:00 committed by GitHub
parent e3f5e7001f
commit 719c96ecd8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 375 additions and 389 deletions

View File

@ -1,4 +1,4 @@
//! Handles modal dialogs that appear as floating menus in the center of the editor window.
//! Handles dialogs that appear as floating menus in the center of the editor window.
//!
//! Dialogs are represented as structs that implement the `DialogLayoutHolder` trait.
//!

View File

@ -1,7 +1,7 @@
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
/// A dialog for confirming the closing of all documents viewable via `file -> close all` in the menu bar.
/// A dialog for confirming the closing of all documents viewable via `File -> Close All` in the menu bar.
pub struct CloseAllDocumentsDialog {
pub unsaved_document_names: Vec<String>,
}

View File

@ -196,10 +196,10 @@ impl<F: Fn(&MessageDiscriminant) -> Vec<KeysGroup>> MessageHandler<LayoutMessage
let callback_message = (parameter_expose_button.on_update.callback)(parameter_expose_button);
responses.add(callback_message);
}
Widget::PivotAssist(pivot_assist) => {
let update_value = value.as_str().expect("RadioInput update was not of type: u64");
pivot_assist.position = update_value.into();
let callback_message = (pivot_assist.on_update.callback)(pivot_assist);
Widget::PivotInput(pivot_input) => {
let update_value = value.as_str().expect("PivotInput update was not of type: u64");
pivot_input.position = update_value.into();
let callback_message = (pivot_input.on_update.callback)(pivot_input);
responses.add(callback_message);
}
Widget::PopoverButton(_) => {}
@ -210,7 +210,6 @@ impl<F: Fn(&MessageDiscriminant) -> Vec<KeysGroup>> MessageHandler<LayoutMessage
responses.add(callback_message);
}
Widget::Separator(_) => {}
Widget::SwatchPairInput(_) => {}
Widget::TextAreaInput(text_area_input) => {
let update_value = value.as_str().expect("TextAreaInput update was not of type: string");
text_area_input.value = update_value.into();
@ -228,6 +227,7 @@ impl<F: Fn(&MessageDiscriminant) -> Vec<KeysGroup>> MessageHandler<LayoutMessage
responses.add(callback_message);
}
Widget::TextLabel(_) => {}
Widget::WorkingColorsButton(_) => {}
};
responses.add(ResendActiveWidget { layout_target, dirty_id: widget_id });
}

View File

@ -1,4 +1,3 @@
use super::widgets::assist_widgets::*;
use super::widgets::button_widgets::*;
use super::widgets::input_widgets::*;
use super::widgets::label_widgets::*;
@ -337,7 +336,7 @@ impl LayoutGroup {
Widget::TextInput(x) => &mut x.tooltip,
Widget::TextLabel(x) => &mut x.tooltip,
Widget::BreadcrumbTrailButtons(x) => &mut x.tooltip,
Widget::InvisibleStandinInput(_) | Widget::PivotAssist(_) | Widget::RadioInput(_) | Widget::Separator(_) | Widget::SwatchPairInput(_) => continue,
Widget::InvisibleStandinInput(_) | Widget::PivotInput(_) | Widget::RadioInput(_) | Widget::Separator(_) | Widget::WorkingColorsButton(_) => continue,
};
if val.is_empty() {
*val = tooltip.clone();
@ -483,15 +482,15 @@ pub enum Widget {
NumberInput(NumberInput),
OptionalInput(OptionalInput),
ParameterExposeButton(ParameterExposeButton),
PivotAssist(PivotAssist),
PivotInput(PivotInput),
PopoverButton(PopoverButton),
RadioInput(RadioInput),
Separator(Separator),
SwatchPairInput(SwatchPairInput),
TextAreaInput(TextAreaInput),
TextButton(TextButton),
TextInput(TextInput),
TextLabel(TextLabel),
WorkingColorsButton(WorkingColorsButton),
}
/// A single change to part of the UI, containing the location of the change and the new value.
@ -559,13 +558,13 @@ impl DiffUpdate {
| Widget::ImageLabel(_)
| Widget::CurveInput(_)
| Widget::InvisibleStandinInput(_)
| Widget::PivotAssist(_)
| Widget::PivotInput(_)
| Widget::RadioInput(_)
| Widget::Separator(_)
| Widget::SwatchPairInput(_)
| Widget::TextAreaInput(_)
| Widget::TextInput(_)
| Widget::TextLabel(_) => None,
| Widget::TextLabel(_)
| Widget::WorkingColorsButton(_) => None,
};
if let Some((tooltip, Some(tooltip_shortcut))) = &mut tooltip_shortcut {
apply_shortcut_to_tooltip(tooltip_shortcut, tooltip);

View File

@ -3,7 +3,6 @@ pub mod widgets;
pub mod widget_prelude {
pub use super::layout_widget::*;
pub use super::widgets::assist_widgets::*;
pub use super::widgets::button_widgets::*;
pub use super::widgets::input_widgets::*;
pub use super::widgets::label_widgets::*;

View File

@ -1,102 +0,0 @@
use crate::messages::layout::utility_types::widget_prelude::*;
use graphite_proc_macros::WidgetBuilder;
use derivative::*;
use glam::DVec2;
use serde::{Deserialize, Serialize};
#[derive(Clone, Default, Derivative, Serialize, Deserialize, WidgetBuilder, specta::Type)]
#[derivative(Debug, PartialEq)]
pub struct PivotAssist {
#[widget_builder(constructor)]
pub position: PivotPosition,
pub disabled: bool,
// Callbacks
#[serde(skip)]
#[derivative(Debug = "ignore", PartialEq = "ignore")]
pub on_update: WidgetCallback<PivotAssist>,
}
#[derive(Clone, Copy, Serialize, Deserialize, Debug, Default, PartialEq, Eq, specta::Type)]
pub enum PivotPosition {
#[default]
None,
TopLeft,
TopCenter,
TopRight,
CenterLeft,
Center,
CenterRight,
BottomLeft,
BottomCenter,
BottomRight,
}
impl From<&str> for PivotPosition {
fn from(input: &str) -> Self {
match input {
"None" => PivotPosition::None,
"TopLeft" => PivotPosition::TopLeft,
"TopCenter" => PivotPosition::TopCenter,
"TopRight" => PivotPosition::TopRight,
"CenterLeft" => PivotPosition::CenterLeft,
"Center" => PivotPosition::Center,
"CenterRight" => PivotPosition::CenterRight,
"BottomLeft" => PivotPosition::BottomLeft,
"BottomCenter" => PivotPosition::BottomCenter,
"BottomRight" => PivotPosition::BottomRight,
_ => panic!("Failed parsing unrecognized PivotPosition enum value '{input}'"),
}
}
}
impl From<PivotPosition> for Option<DVec2> {
fn from(input: PivotPosition) -> Self {
match input {
PivotPosition::None => None,
PivotPosition::TopLeft => Some(DVec2::new(0., 0.)),
PivotPosition::TopCenter => Some(DVec2::new(0.5, 0.)),
PivotPosition::TopRight => Some(DVec2::new(1., 0.)),
PivotPosition::CenterLeft => Some(DVec2::new(0., 0.5)),
PivotPosition::Center => Some(DVec2::new(0.5, 0.5)),
PivotPosition::CenterRight => Some(DVec2::new(1., 0.5)),
PivotPosition::BottomLeft => Some(DVec2::new(0., 1.)),
PivotPosition::BottomCenter => Some(DVec2::new(0.5, 1.)),
PivotPosition::BottomRight => Some(DVec2::new(1., 1.)),
}
}
}
impl From<DVec2> for PivotPosition {
fn from(input: DVec2) -> Self {
const TOLERANCE: f64 = 1e-5_f64;
if input.y.abs() < TOLERANCE {
if input.x.abs() < TOLERANCE {
return PivotPosition::TopLeft;
} else if (input.x - 0.5).abs() < TOLERANCE {
return PivotPosition::TopCenter;
} else if (input.x - 1.).abs() < TOLERANCE {
return PivotPosition::TopRight;
}
} else if (input.y - 0.5).abs() < TOLERANCE {
if input.x.abs() < TOLERANCE {
return PivotPosition::CenterLeft;
} else if (input.x - 0.5).abs() < TOLERANCE {
return PivotPosition::Center;
} else if (input.x - 1.).abs() < TOLERANCE {
return PivotPosition::CenterRight;
}
} else if (input.y - 1.).abs() < TOLERANCE {
if input.x.abs() < TOLERANCE {
return PivotPosition::BottomLeft;
} else if (input.x - 0.5).abs() < TOLERANCE {
return PivotPosition::BottomCenter;
} else if (input.x - 1.).abs() < TOLERANCE {
return PivotPosition::BottomRight;
}
}
PivotPosition::None
}
}

View File

@ -106,6 +106,16 @@ pub struct TextButton {
pub on_update: WidgetCallback<TextButton>,
}
#[derive(Clone, Serialize, Deserialize, Derivative, WidgetBuilder, specta::Type)]
#[derivative(Debug, PartialEq, Default)]
pub struct WorkingColorsButton {
#[widget_builder(constructor)]
pub primary: Color,
#[widget_builder(constructor)]
pub secondary: Color,
}
#[derive(Clone, Derivative, Serialize, Deserialize, WidgetBuilder, specta::Type)]
#[derivative(Debug, PartialEq, Default)]
pub struct ColorButton {

View File

@ -3,11 +3,11 @@ use crate::messages::layout::utility_types::widget_prelude::*;
use document_legacy::layers::layer_info::LayerDataTypeDiscriminant;
use document_legacy::LayerId;
use graphene_core::raster::color::Color;
use graphene_core::raster::curve::Curve;
use graphite_proc_macros::WidgetBuilder;
use derivative::*;
use glam::DVec2;
use serde::{Deserialize, Serialize};
#[derive(Clone, Derivative, Serialize, Deserialize, WidgetBuilder, specta::Type)]
@ -339,16 +339,6 @@ pub struct RadioEntryData {
pub on_update: WidgetCallback<()>,
}
#[derive(Clone, Serialize, Deserialize, Derivative, WidgetBuilder, specta::Type)]
#[derivative(Debug, PartialEq, Default)]
pub struct SwatchPairInput {
#[widget_builder(constructor)]
pub primary: Color,
#[widget_builder(constructor)]
pub secondary: Color,
}
#[derive(Clone, Serialize, Deserialize, Derivative, WidgetBuilder, specta::Type)]
#[derivative(Debug, PartialEq, Default)]
pub struct TextAreaInput {
@ -405,3 +395,99 @@ pub struct CurveInput {
#[derivative(Debug = "ignore", PartialEq = "ignore")]
pub on_update: WidgetCallback<CurveInput>,
}
#[derive(Clone, Default, Derivative, Serialize, Deserialize, WidgetBuilder, specta::Type)]
#[derivative(Debug, PartialEq)]
pub struct PivotInput {
#[widget_builder(constructor)]
pub position: PivotPosition,
pub disabled: bool,
// Callbacks
#[serde(skip)]
#[derivative(Debug = "ignore", PartialEq = "ignore")]
pub on_update: WidgetCallback<PivotInput>,
}
#[derive(Clone, Copy, Serialize, Deserialize, Debug, Default, PartialEq, Eq, specta::Type)]
pub enum PivotPosition {
#[default]
None,
TopLeft,
TopCenter,
TopRight,
CenterLeft,
Center,
CenterRight,
BottomLeft,
BottomCenter,
BottomRight,
}
impl From<&str> for PivotPosition {
fn from(input: &str) -> Self {
match input {
"None" => PivotPosition::None,
"TopLeft" => PivotPosition::TopLeft,
"TopCenter" => PivotPosition::TopCenter,
"TopRight" => PivotPosition::TopRight,
"CenterLeft" => PivotPosition::CenterLeft,
"Center" => PivotPosition::Center,
"CenterRight" => PivotPosition::CenterRight,
"BottomLeft" => PivotPosition::BottomLeft,
"BottomCenter" => PivotPosition::BottomCenter,
"BottomRight" => PivotPosition::BottomRight,
_ => panic!("Failed parsing unrecognized PivotPosition enum value '{input}'"),
}
}
}
impl From<PivotPosition> for Option<DVec2> {
fn from(input: PivotPosition) -> Self {
match input {
PivotPosition::None => None,
PivotPosition::TopLeft => Some(DVec2::new(0., 0.)),
PivotPosition::TopCenter => Some(DVec2::new(0.5, 0.)),
PivotPosition::TopRight => Some(DVec2::new(1., 0.)),
PivotPosition::CenterLeft => Some(DVec2::new(0., 0.5)),
PivotPosition::Center => Some(DVec2::new(0.5, 0.5)),
PivotPosition::CenterRight => Some(DVec2::new(1., 0.5)),
PivotPosition::BottomLeft => Some(DVec2::new(0., 1.)),
PivotPosition::BottomCenter => Some(DVec2::new(0.5, 1.)),
PivotPosition::BottomRight => Some(DVec2::new(1., 1.)),
}
}
}
impl From<DVec2> for PivotPosition {
fn from(input: DVec2) -> Self {
const TOLERANCE: f64 = 1e-5_f64;
if input.y.abs() < TOLERANCE {
if input.x.abs() < TOLERANCE {
return PivotPosition::TopLeft;
} else if (input.x - 0.5).abs() < TOLERANCE {
return PivotPosition::TopCenter;
} else if (input.x - 1.).abs() < TOLERANCE {
return PivotPosition::TopRight;
}
} else if (input.y - 0.5).abs() < TOLERANCE {
if input.x.abs() < TOLERANCE {
return PivotPosition::CenterLeft;
} else if (input.x - 0.5).abs() < TOLERANCE {
return PivotPosition::Center;
} else if (input.x - 1.).abs() < TOLERANCE {
return PivotPosition::CenterRight;
}
} else if (input.y - 1.).abs() < TOLERANCE {
if input.x.abs() < TOLERANCE {
return PivotPosition::BottomLeft;
} else if (input.x - 0.5).abs() < TOLERANCE {
return PivotPosition::BottomCenter;
} else if (input.x - 1.).abs() < TOLERANCE {
return PivotPosition::BottomRight;
}
}
PivotPosition::None
}
}

View File

@ -1,4 +1,3 @@
pub mod assist_widgets;
pub mod button_widgets;
pub mod input_widgets;
pub mod label_widgets;

View File

@ -1181,8 +1181,8 @@ pub fn transform_properties(document_node: &DocumentNode, node_id: NodeId, _cont
{
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
widgets.push(
PivotAssist::new(pivot.into())
.on_update(update_value(|pivot: &PivotAssist| TaggedValue::DVec2(Into::<Option<DVec2>>::into(pivot.position).unwrap()), node_id, 5))
PivotInput::new(pivot.into())
.on_update(update_value(|pivot: &PivotInput| TaggedValue::DVec2(Into::<Option<DVec2>>::into(pivot.position).unwrap()), node_id, 5))
.widget_holder(),
);
} else {

View File

@ -167,8 +167,8 @@ fn node_section_transform(layer: &Layer, persistent_data: &PersistentData) -> La
widgets: vec![
TextLabel::new("Location").widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
PivotAssist::new(layer.pivot.into())
.on_update(|pivot_assist: &PivotAssist| PropertiesPanelMessage::SetPivot { new_position: pivot_assist.position }.into())
PivotInput::new(layer.pivot.into())
.on_update(|pivot_input: &PivotInput| PropertiesPanelMessage::SetPivot { new_position: pivot_input.position }.into())
.widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
NumberInput::new(Some(layer.transform.x() + pivot.x))

View File

@ -116,8 +116,8 @@ impl SelectTool {
// }
fn pivot_widget(&self, disabled: bool) -> WidgetHolder {
PivotAssist::new(self.tool_data.pivot.to_pivot_position())
.on_update(|pivot_assist: &PivotAssist| SelectToolMessage::SetPivot { position: pivot_assist.position }.into())
PivotInput::new(self.tool_data.pivot.to_pivot_position())
.on_update(|pivot_input: &PivotInput| SelectToolMessage::SetPivot { position: pivot_input.position }.into())
.disabled(disabled)
.widget_holder()
}

View File

@ -140,7 +140,7 @@ impl DocumentToolData {
pub fn update_working_colors(&self, responses: &mut VecDeque<Message>) {
let layout = WidgetLayout::new(vec![
LayoutGroup::Row {
widgets: vec![SwatchPairInput::new(self.primary_color, self.secondary_color).widget_holder()],
widgets: vec![WorkingColorsButton::new(self.primary_color, self.secondary_color).widget_holder()],
},
LayoutGroup::Row {
widgets: vec![

View File

@ -272,10 +272,10 @@
.popover-button,
.color-button > button,
.color-picker .preset-color,
.swatch-pair .swatch > button,
.working-colors-button .swatch > button,
.radio-input button,
.menu-list,
.menu-bar-input .entry,
.menu-list-button .entry,
.layer-tree .expand-arrow,
.widget-section .header {
&:focus-visible {

View File

@ -4,7 +4,7 @@ Each component represents a (usually reusable) part of the Graphite editor GUI.
## Floating Menus: `floating-menus/`
The temporary UI areas with dark backgrounds which hover over the top of the editor window content. Examples include popovers, dropdown menu selectors, and dialog modals.
The temporary UI areas with dark backgrounds which hover over the top of the editor window content. Examples include menu lists, popovers, and dialogs.
## Layout: `layout/`

View File

@ -319,7 +319,7 @@
<TextLabel tooltip="Red/Green/Blue channels of the color, integers 0255">RGB</TextLabel>
<Separator />
<LayoutRow>
{#each rgbChannels as [channel, strength], index (channel)}
{#each rgbChannels as [channel, strength], index}
{#if index > 0}
<Separator type="Related" />
{/if}
@ -343,7 +343,7 @@
>
<Separator />
<LayoutRow>
{#each hsvChannels as [channel, strength], index (channel)}
{#each hsvChannels as [channel, strength], index}
{#if index > 0}
<Separator type="Related" />
{/if}

View File

@ -26,7 +26,7 @@
</script>
<!-- TODO: Use https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog for improved accessibility -->
<FloatingMenu open={true} class="dialog-modal" type="Dialog" direction="Center" bind:this={self} data-dialog-modal>
<FloatingMenu open={true} class="dialog" type="Dialog" direction="Center" bind:this={self} data-dialog>
<LayoutRow class="header-area">
<!-- `$dialog.icon` class exists to provide special sizing in CSS to specific icons -->
<IconLabel icon={$dialog.icon} class={$dialog.icon.toLowerCase()} />
@ -39,11 +39,11 @@
{/if}
{#if $dialog.panicDetails}
<div class="widget-layout details">
<div class="widget-row"><TextLabel bold={true}>The editor crashed sorry about that</TextLabel></div>
<div class="widget-row"><TextLabel>Please report this by filing an issue on GitHub:</TextLabel></div>
<div class="widget-row"><TextButton label="Report Bug" icon="Warning" noBackground={true} action={() => window.open(githubUrl($dialog.panicDetails), "_blank")} /></div>
<div class="widget-row"><TextLabel multiline={true}>Reload the editor to continue. If this occurs<br />immediately on repeated reloads, clear storage:</TextLabel></div>
<div class="widget-row">
<div class="widget-span row"><TextLabel bold={true}>The editor crashed sorry about that</TextLabel></div>
<div class="widget-span row"><TextLabel>Please report this by filing an issue on GitHub:</TextLabel></div>
<div class="widget-span row"><TextButton label="Report Bug" icon="Warning" noBackground={true} action={() => window.open(githubUrl($dialog.panicDetails), "_blank")} /></div>
<div class="widget-span row"><TextLabel multiline={true}>Reload the editor to continue. If this occurs<br />immediately on repeated reloads, clear storage:</TextLabel></div>
<div class="widget-span row">
<TextButton
label="Clear Saved Documents"
icon="Trash"
@ -75,7 +75,7 @@
</FloatingMenu>
<style lang="scss" global>
.dialog-modal {
.dialog {
position: absolute;
pointer-events: none;
width: 100%;

View File

@ -103,7 +103,7 @@
const menuOpen = open;
const flatEntries = entries.flat().filter((entry) => !entry.disabled);
const openChild = flatEntries.findIndex((entry) => entry.children?.length && entry.ref?.open);
const openChild = flatEntries.findIndex((entry) => (entry.children?.length ?? 0) > 0 && entry.ref?.open);
const openSubmenu = (highlightedEntry: MenuListEntry) => {
if (highlightedEntry.ref && highlightedEntry.children?.length) {

View File

@ -27,12 +27,12 @@
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
import Graph from "@graphite/components/views/Graph.svelte";
import CanvasRuler from "@graphite/components/widgets/metrics/CanvasRuler.svelte";
import PersistentScrollbar from "@graphite/components/widgets/metrics/PersistentScrollbar.svelte";
import RulerInput from "@graphite/components/widgets/inputs/RulerInput.svelte";
import ScrollbarInput from "@graphite/components/widgets/inputs/ScrollbarInput.svelte";
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
let rulerHorizontal: CanvasRuler | undefined;
let rulerVertical: CanvasRuler | undefined;
let rulerHorizontal: RulerInput | undefined;
let rulerVertical: RulerInput | undefined;
let viewport: HTMLDivElement | undefined;
const editor = getContext<Editor>("editor");
@ -441,11 +441,11 @@
</LayoutCol>
<LayoutCol class="table">
<LayoutRow class="ruler-or-scrollbar top-ruler">
<CanvasRuler 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 class="viewport-container">
<LayoutCol class="ruler-or-scrollbar">
<CanvasRuler 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 class="viewport-container" styles={{ cursor: canvasCursor }}>
{#if cursorEyedropper}
@ -482,7 +482,7 @@
</div>
</LayoutCol>
<LayoutCol class="ruler-or-scrollbar right-scrollbar">
<PersistentScrollbar
<ScrollbarInput
direction="Vertical"
handleLength={scrollbarSize.y}
handlePosition={scrollbarPos.y}
@ -492,7 +492,7 @@
</LayoutCol>
</LayoutRow>
<LayoutRow class="ruler-or-scrollbar bottom-scrollbar">
<PersistentScrollbar
<ScrollbarInput
direction="Horizontal"
handleLength={scrollbarSize.x}
handlePosition={scrollbarPos.x}
@ -572,10 +572,10 @@
.widget-layout:last-of-type {
height: auto;
.widget-row {
.widget-span.row {
min-height: 0;
.swatch-pair {
.working-colors-button {
margin: 0;
}
@ -594,16 +594,16 @@
flex: 0 0 auto;
}
.top-ruler .canvas-ruler {
.top-ruler .ruler-input {
padding-left: 16px;
margin-right: 16px;
}
.right-scrollbar .persistent-scrollbar {
.right-scrollbar .scrollbar-input {
margin-top: -16px;
}
.bottom-scrollbar .persistent-scrollbar {
.bottom-scrollbar .scrollbar-input {
margin-right: 16px;
}

View File

@ -1,8 +1,8 @@
<script lang="ts">
import { isWidgetColumn, isWidgetRow, isWidgetSection, type WidgetLayout } from "@graphite/wasm-communication/messages";
import { isWidgetSpanColumn, isWidgetSpanRow, isWidgetSection, type WidgetLayout } from "@graphite/wasm-communication/messages";
import WidgetSection from "@graphite/components/widgets/groups/WidgetSection.svelte";
import WidgetRow from "@graphite/components/widgets/WidgetRow.svelte";
import WidgetSection from "@graphite/components/widgets/WidgetSection.svelte";
import WidgetSpan from "@graphite/components/widgets/WidgetSpan.svelte";
export let layout: WidgetLayout;
let className = "";
@ -14,15 +14,14 @@
.join(" ");
</script>
<!-- TODO: Refactor this component (together with `WidgetRow.svelte`) to be more logically consistent with our layout definition goals, in terms of naming and capabilities -->
<div class={`widget-layout ${className} ${extraClasses}`.trim()}>
{#each layout.layout as layoutGroup, index (index)}
{#if isWidgetColumn(layoutGroup) || isWidgetRow(layoutGroup)}
<WidgetRow widgetData={layoutGroup} layoutTarget={layout.layoutTarget} />
{#each layout.layout as layoutGroup}
{#if isWidgetSpanRow(layoutGroup) || isWidgetSpanColumn(layoutGroup)}
<WidgetSpan widgetData={layoutGroup} layoutTarget={layout.layoutTarget} />
{:else if isWidgetSection(layoutGroup)}
<WidgetSection widgetData={layoutGroup} layoutTarget={layout.layoutTarget} />
{:else}
<span style="color: #d6536e">Error: The widget row that belongs here has an invalid layout group type</span>
<span style="color: #d6536e">Error: The widget layout that belongs here has an invalid layout group type</span>
{/if}
{/each}
</div>

View File

@ -1,9 +1,9 @@
<script lang="ts">
import { isWidgetRow, isWidgetSection, type WidgetSection as WidgetSectionFromJsMessages } from "@graphite/wasm-communication/messages";
import { isWidgetSpanRow, isWidgetSpanColumn, isWidgetSection, type WidgetSection as WidgetSectionFromJsMessages } from "@graphite/wasm-communication/messages";
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
import WidgetRow from "@graphite/components/widgets/WidgetRow.svelte";
import WidgetSpan from "@graphite/components/widgets/WidgetSpan.svelte";
export let widgetData: WidgetSectionFromJsMessages;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -20,9 +20,11 @@
</button>
{#if expanded}
<LayoutCol class="body">
{#each widgetData.layout as layoutGroup, index (index)}
{#if isWidgetRow(layoutGroup)}
<WidgetRow widgetData={layoutGroup} {layoutTarget} />
{#each widgetData.layout as layoutGroup}
{#if isWidgetSpanRow(layoutGroup)}
<WidgetSpan widgetData={layoutGroup} {layoutTarget} />
{:else if isWidgetSpanColumn(layoutGroup)}
<span style="color: #d6536e">Error: The WidgetSpan used here should be a row not a column</span>
{:else if isWidgetSection(layoutGroup)}
<svelte:self widgetData={layoutGroup} {layoutTarget} />
{:else}
@ -105,7 +107,7 @@
border-radius: 0 0 4px 4px;
overflow: hidden;
.widget-row {
.widget-span.row {
&:first-child {
margin-top: calc(4px - 1px);
}

View File

@ -1,19 +1,18 @@
<script lang="ts">
import { getContext } from "svelte";
import ColorButton from "./buttons/ColorButton.svelte";
import { debouncer } from "@graphite/utility-functions/debounce";
import type { Editor } from "@graphite/wasm-communication/editor";
import type { Widget, WidgetColumn, WidgetRow } from "@graphite/wasm-communication/messages";
import { narrowWidgetProps, isWidgetColumn, isWidgetRow } from "@graphite/wasm-communication/messages";
import type { Widget, WidgetSpanColumn, WidgetSpanRow } from "@graphite/wasm-communication/messages";
import { narrowWidgetProps, isWidgetSpanColumn, isWidgetSpanRow } from "@graphite/wasm-communication/messages";
import PivotAssist from "@graphite/components/widgets/assists/PivotAssist.svelte";
import BreadcrumbTrailButtons from "@graphite/components/widgets/buttons/BreadcrumbTrailButtons.svelte";
import ColorButton from "@graphite/components/widgets/buttons/ColorButton.svelte";
import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte";
import ParameterExposeButton from "@graphite/components/widgets/buttons/ParameterExposeButton.svelte";
import PopoverButton from "@graphite/components/widgets/buttons/PopoverButton.svelte";
import TextButton from "@graphite/components/widgets/buttons/TextButton.svelte";
import WorkingColorsButton from "@graphite/components/widgets/buttons/WorkingColorsButton.svelte";
import CheckboxInput from "@graphite/components/widgets/inputs/CheckboxInput.svelte";
import CurveInput from "@graphite/components/widgets/inputs/CurveInput.svelte";
import DropdownInput from "@graphite/components/widgets/inputs/DropdownInput.svelte";
@ -21,8 +20,8 @@
import LayerReferenceInput from "@graphite/components/widgets/inputs/LayerReferenceInput.svelte";
import NumberInput from "@graphite/components/widgets/inputs/NumberInput.svelte";
import OptionalInput from "@graphite/components/widgets/inputs/OptionalInput.svelte";
import PivotInput from "@graphite/components/widgets/inputs/PivotInput.svelte";
import RadioInput from "@graphite/components/widgets/inputs/RadioInput.svelte";
import SwatchPairInput from "@graphite/components/widgets/inputs/SwatchPairInput.svelte";
import TextAreaInput from "@graphite/components/widgets/inputs/TextAreaInput.svelte";
import TextInput from "@graphite/components/widgets/inputs/TextInput.svelte";
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
@ -35,7 +34,7 @@
const editor = getContext<Editor>("editor");
export let widgetData: WidgetColumn | WidgetRow;
export let widgetData: WidgetSpanRow | WidgetSpanColumn;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export let layoutTarget: any;
@ -43,16 +42,15 @@
$: widgets = watchWidgets(widgetData);
$: widgetsAndNextSiblingIsSuffix = watchWidgetsAndNextSiblingIsSuffix(widgets);
function watchDirection(widgetData: WidgetRow | WidgetColumn): "row" | "column" | "ERROR" {
if (isWidgetRow(widgetData)) return "row";
if (isWidgetColumn(widgetData)) return "column";
return "ERROR";
function watchDirection(widgetData: WidgetSpanRow | WidgetSpanColumn): "row" | "column" | undefined {
if (isWidgetSpanRow(widgetData)) return "row";
if (isWidgetSpanColumn(widgetData)) return "column";
}
function watchWidgets(widgetData: WidgetRow | WidgetColumn): Widget[] {
function watchWidgets(widgetData: WidgetSpanRow | WidgetSpanColumn): Widget[] {
let widgets: Widget[] = [];
if (isWidgetRow(widgetData)) widgets = widgetData.rowWidgets;
else if (isWidgetColumn(widgetData)) widgets = widgetData.columnWidgets;
if (isWidgetSpanRow(widgetData)) widgets = widgetData.rowWidgets;
else if (isWidgetSpanColumn(widgetData)) widgets = widgetData.columnWidgets;
return widgets;
}
@ -83,8 +81,8 @@
<!-- TODO: Refactor this component to use `<svelte:component this={attributesObject} />` to avoid all the separate conditional components -->
<!-- TODO: Also rename this component, and probably move the `widget-${direction}` wrapper to be part of `WidgetLayout.svelte` as part of its refactor -->
<div class={`widget-${direction}`}>
{#each widgetsAndNextSiblingIsSuffix as [component, nextIsSuffix], index (index)}
<div class="widget-span" class:row={direction === "row"} class:column={direction === "column"}>
{#each widgetsAndNextSiblingIsSuffix as [component, nextIsSuffix], index}
{@const checkboxInput = narrowWidgetProps(component.props, "CheckboxInput")}
{#if checkboxInput}
<CheckboxInput {...exclude(checkboxInput)} on:checked={({ detail }) => updateLayout(index, detail)} />
@ -139,9 +137,9 @@
{#if optionalInput}
<OptionalInput {...exclude(optionalInput)} on:checked={({ detail }) => updateLayout(index, detail)} />
{/if}
{@const pivotAssist = narrowWidgetProps(component.props, "PivotAssist")}
{#if pivotAssist}
<PivotAssist {...exclude(pivotAssist)} on:position={({ detail }) => updateLayout(index, detail)} />
{@const pivotInput = narrowWidgetProps(component.props, "PivotInput")}
{#if pivotInput}
<PivotInput {...exclude(pivotInput)} on:position={({ detail }) => updateLayout(index, detail)} />
{/if}
{@const popoverButton = narrowWidgetProps(component.props, "PopoverButton")}
{#if popoverButton}
@ -162,9 +160,9 @@
{#if separator}
<Separator {...exclude(separator)} />
{/if}
{@const swatchPairInput = narrowWidgetProps(component.props, "SwatchPairInput")}
{#if swatchPairInput}
<SwatchPairInput {...exclude(swatchPairInput)} />
{@const workingColorsButton = narrowWidgetProps(component.props, "WorkingColorsButton")}
{#if workingColorsButton}
<WorkingColorsButton {...exclude(workingColorsButton)} />
{/if}
{@const textAreaInput = narrowWidgetProps(component.props, "TextAreaInput")}
{#if textAreaInput}
@ -190,13 +188,13 @@
</div>
<style lang="scss" global>
.widget-column {
.widget-span.column {
flex: 0 0 auto;
display: flex;
flex-direction: column;
}
.widget-row.widget-row {
.widget-span.row {
flex: 0 0 auto;
display: flex;
min-height: 32px;

View File

@ -10,7 +10,7 @@
</script>
<LayoutRow class="breadcrumb-trail-buttons" {tooltip}>
{#each labels as label, index (index)}
{#each labels as label, index}
<TextButton {label} emphasized={index === labels.length - 1} {disabled} action={() => !disabled && index !== labels.length - 1 && action(index)} />
{/each}
</LayoutRow>

View File

@ -0,0 +1,83 @@
<script lang="ts">
import type { MenuListEntry } from "@graphite/wasm-communication/messages";
import MenuList from "@graphite/components/floating-menus/MenuList.svelte";
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
export let entry: MenuListEntry;
let entryRef: MenuList;
$: (entry.ref = entryRef), entry.ref;
function clickEntry(e: MouseEvent) {
// If there's no menu to open, trigger the action but don't try to open its non-existant children
if ((entry.children?.length ?? 0) === 0) {
if (entry.action && !entry.disabled) entry.action();
return;
}
// Focus the target so that keyboard inputs are sent to the dropdown
(e.target as HTMLElement | undefined)?.focus();
if (entry.ref) {
entry.ref.open = true;
} else {
throw new Error("The menu bar floating menu has no associated ref");
}
}
</script>
<div class="menu-list-button">
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<div
on:click={(e) => clickEntry(e)}
on:keydown={(e) => entry.ref?.keydown(e, false)}
class="entry"
class:open={entry.ref?.open}
tabindex="0"
data-floating-menu-spawner={(entry.children?.length ?? 0) > 0 ? "" : "no-hover-transfer"}
>
{#if entry.icon}
<IconLabel icon={entry.icon} />
{/if}
{#if entry.label}
<TextLabel>{entry.label}</TextLabel>
{/if}
</div>
{#if (entry.children?.length ?? 0) > 0}
<MenuList
on:open={({ detail }) => entry.ref && (entry.ref.open = detail)}
open={entry.ref?.open || false}
entries={entry.children || []}
direction="Bottom"
minWidth={240}
drawIcon={true}
bind:this={entryRef}
/>
{/if}
</div>
<style lang="scss" global>
.menu-list-button {
display: flex;
position: relative;
.entry {
display: flex;
align-items: center;
white-space: nowrap;
background: none;
padding: 0 8px;
margin: 0;
border: 0;
border-radius: 2px;
&:hover,
&.open {
background: var(--color-5-dullgray);
}
}
}
</style>

View File

@ -88,12 +88,12 @@
}
}
.widget-row > & + .text-button,
.widget-span.row > & + .text-button,
.layout-row > & + .text-button {
margin-left: 8px;
}
.widget-column > & + .text-button,
.widget-span.column > & + .text-button,
.layout-column > & + .text-button {
margin-top: 8px;
}

View File

@ -35,7 +35,7 @@
}
</script>
<LayoutCol class="swatch-pair">
<LayoutCol class="working-colors-button">
<LayoutRow class="primary swatch">
<button on:click={clickPrimarySwatch} class:open={primaryOpen} style:--swatch-color={primary.toRgbaCSS()} data-floating-menu-spawner="no-hover-transfer" tabindex="0" />
<ColorPicker open={primaryOpen} on:open={({ detail }) => (primaryOpen = detail)} color={primary} on:color={({ detail }) => primaryColorChanged(detail)} direction="Right" />
@ -47,7 +47,7 @@
</LayoutCol>
<style lang="scss" global>
.swatch-pair {
.working-colors-button {
flex: 0 0 auto;
.swatch {

View File

@ -1,140 +0,0 @@
<script lang="ts">
import { getContext, onMount } from "svelte";
import { platformIsMac } from "@graphite/utility-functions/platform";
import type { Editor } from "@graphite/wasm-communication/editor";
import { type KeyRaw, type LayoutKeysGroup, type MenuBarEntry, type MenuListEntry, UpdateMenuBarLayout } from "@graphite/wasm-communication/messages";
import MenuList from "@graphite/components/floating-menus/MenuList.svelte";
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
// TODO: Apparently, Safari does not support the Keyboard.lock() API but does relax its authority over certain keyboard shortcuts in fullscreen mode, which we should take advantage of
const accelKey = platformIsMac() ? "Command" : "Control";
const LOCK_REQUIRING_SHORTCUTS: KeyRaw[][] = [
[accelKey, "KeyW"],
[accelKey, "KeyN"],
[accelKey, "Shift", "KeyN"],
[accelKey, "KeyT"],
[accelKey, "Shift", "KeyT"],
];
const editor = getContext<Editor>("editor");
let entries: MenuListEntry[] = [];
function clickEntry(menuListEntry: MenuListEntry, e: MouseEvent) {
// If there's no menu to open, trigger the action but don't try to open its non-existant children
if (!menuListEntry.children || menuListEntry.children.length === 0) {
if (menuListEntry.action && !menuListEntry.disabled) menuListEntry.action();
return;
}
// Focus the target so that keyboard inputs are sent to the dropdown
(e.target as HTMLElement | undefined)?.focus();
if (menuListEntry.ref) {
menuListEntry.ref.open = true;
entries = entries;
} else {
throw new Error("The menu bar floating menu has no associated ref");
}
}
onMount(() => {
editor.subscriptions.subscribeJsMessage(UpdateMenuBarLayout, (updateMenuBarLayout) => {
const arraysEqual = (a: KeyRaw[], b: KeyRaw[]): boolean => a.length === b.length && a.every((aValue, i) => aValue === b[i]);
const shortcutRequiresLock = (shortcut: LayoutKeysGroup): boolean => {
const shortcutKeys = shortcut.map((keyWithLabel) => keyWithLabel.key);
// If this shortcut matches any of the browser-reserved shortcuts
return LOCK_REQUIRING_SHORTCUTS.some((lockKeyCombo) => arraysEqual(shortcutKeys, lockKeyCombo));
};
const menuBarEntryToMenuListEntry = (entry: MenuBarEntry): MenuListEntry => ({
// From `MenuEntryCommon`
...entry,
// Shared names with fields that need to be converted from the type used in `MenuBarEntry` to that of `MenuListEntry`
action: () => editor.instance.updateLayout(updateMenuBarLayout.layoutTarget, entry.action.widgetId, undefined),
children: entry.children ? entry.children.map((entries) => entries.map((entry) => menuBarEntryToMenuListEntry(entry))) : undefined,
// New fields in `MenuListEntry`
shortcutRequiresLock: entry.shortcut ? shortcutRequiresLock(entry.shortcut.keys) : undefined,
value: undefined,
disabled: entry.disabled ?? undefined,
font: undefined,
ref: undefined,
});
entries = updateMenuBarLayout.layout.map(menuBarEntryToMenuListEntry);
});
});
</script>
<div class="menu-bar-input" data-menu-bar-input>
{#each entries as entry, index (index)}
<div class="entry-container">
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<div
on:click={(e) => clickEntry(entry, e)}
on:keydown={(e) => entry.ref?.keydown(e, false)}
class="entry"
class:open={entry.ref?.open}
tabindex="0"
data-floating-menu-spawner={entry.children && entry.children.length > 0 ? "" : "no-hover-transfer"}
>
{#if entry.icon}
<IconLabel icon={entry.icon} />
{/if}
{#if entry.label}
<TextLabel>{entry.label}</TextLabel>
{/if}
</div>
{#if entry.children && entry.children.length > 0}
<MenuList
on:open={({ detail }) => {
if (entry.ref) {
entry.ref.open = detail;
entries = entries;
}
}}
open={entry.ref?.open || false}
entries={entry.children || []}
direction="Bottom"
minWidth={240}
drawIcon={true}
bind:this={entry.ref}
/>
{/if}
</div>
{/each}
</div>
<style lang="scss" global>
.menu-bar-input {
display: flex;
.entry-container {
display: flex;
position: relative;
.entry {
display: flex;
align-items: center;
white-space: nowrap;
padding: 0 8px;
background: none;
border: 0;
margin: 0;
border-radius: 2px;
&:hover,
&.open {
background: var(--color-5-dullgray);
}
}
}
}
</style>

View File

@ -14,7 +14,7 @@
}
</script>
<div class="pivot-assist" class:disabled>
<div class="pivot-input" class:disabled>
<button on:click={() => setPosition("TopLeft")} class="row-1 col-1" class:active={position === "TopLeft"} tabindex="-1" {disabled}><div /></button>
<button on:click={() => setPosition("TopCenter")} class="row-1 col-2" class:active={position === "TopCenter"} tabindex="-1" {disabled}><div /></button>
<button on:click={() => setPosition("TopRight")} class="row-1 col-3" class:active={position === "TopRight"} tabindex="-1" {disabled}><div /></button>
@ -27,7 +27,7 @@
</div>
<style lang="scss" global>
.pivot-assist {
.pivot-input {
position: relative;
flex: 0 0 auto;
width: 24px;

View File

@ -24,7 +24,7 @@
</script>
<LayoutRow class="radio-input" classes={{ disabled }}>
{#each entries as entry, index (index)}
{#each entries as entry, index}
<button
class:active={index === selectedIndex}
class:mixed={selectedIndex === undefined}

View File

@ -15,7 +15,7 @@
export let mediumDivisions = 5;
export let minorDivisions = 2;
let canvasRuler: HTMLDivElement | undefined;
let rulerInput: HTMLDivElement | undefined;
let rulerLength = 0;
let svgBounds = { width: "0px", height: "0px" };
@ -76,11 +76,11 @@
}
export function resize() {
if (!canvasRuler) return;
if (!rulerInput) return;
const isVertical = direction === "Vertical";
const newLength = isVertical ? canvasRuler.clientHeight : canvasRuler.clientWidth;
const newLength = isVertical ? rulerInput.clientHeight : rulerInput.clientWidth;
const roundedUp = (Math.floor(newLength / majorMarkSpacing) + 1) * majorMarkSpacing;
if (roundedUp !== rulerLength) {
@ -98,17 +98,17 @@
}
</script>
<div class={`canvas-ruler ${direction.toLowerCase()}`} bind:this={canvasRuler}>
<div class={`ruler-input ${direction.toLowerCase()}`} bind:this={rulerInput}>
<svg style:width={svgBounds.width} style:height={svgBounds.height}>
<path d={svgPath} />
{#each svgTexts as svgText, index (index)}
{#each svgTexts as svgText}
<text transform={svgText.transform}>{svgText.text}</text>
{/each}
</svg>
</div>
<style lang="scss" global>
.canvas-ruler {
.ruler-input {
flex: 1 1 100%;
background: var(--color-4-dimgray);
overflow: hidden;

View File

@ -103,7 +103,7 @@
});
</script>
<div class={`persistent-scrollbar ${direction.toLowerCase()}`}>
<div class={`scrollbar-input ${direction.toLowerCase()}`}>
<button class="arrow decrease" on:pointerdown={() => changePosition(-50)} tabindex="-1" />
<div class="scroll-track" bind:this={scrollTrack} on:pointerdown={grabArea}>
<div class="scroll-thumb" on:pointerdown={grabHandle} class:dragging style:top={thumbTop} style:bottom={thumbBottom} style:left={thumbLeft} style:right={thumbRight} />
@ -112,7 +112,7 @@
</div>
<style lang="scss" global>
.persistent-scrollbar {
.scrollbar-input {
display: flex;
flex: 1 1 100%;

View File

@ -120,11 +120,11 @@
<IconLabel class="user-input-label keyboard-lock-notice" icon="Info" tooltip={keyboardLockInfoMessage} />
{:else}
<LayoutRow class="user-input-label" classes={{ "text-only": textOnly }}>
{#each keysWithLabelsGroups as keysWithLabels, groupIndex (groupIndex)}
{#each keysWithLabelsGroups as keysWithLabels, groupIndex}
{#if groupIndex > 0}
<Separator type="Related" />
{/if}
{#each keyTextOrIconList(keysWithLabels) as keyInfo, keyIndex (keyIndex)}
{#each keyTextOrIconList(keysWithLabels) as keyInfo}
<div class={`input-key ${keyInfo.width}`}>
{#if keyInfo.icon}
<IconLabel icon={keyInfo.icon} />

View File

@ -3,12 +3,15 @@
</script>
<script lang="ts">
import { getContext } from "svelte";
import { getContext, onMount } from "svelte";
import type { PortfolioState } from "@graphite/state-providers/portfolio";
import { platformIsMac } from "@graphite/utility-functions/platform";
import type { Editor } from "@graphite/wasm-communication/editor";
import { type KeyRaw, type LayoutKeysGroup, type MenuBarEntry, type MenuListEntry, UpdateMenuBarLayout } from "@graphite/wasm-communication/messages";
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
import MenuBarInput from "@graphite/components/widgets/inputs/MenuBarInput.svelte";
import MenuListButton from "@graphite/components/widgets/buttons/MenuListButton.svelte";
import WindowButtonsMac from "@graphite/components/window/title-bar/WindowButtonsMac.svelte";
import WindowButtonsWeb from "@graphite/components/window/title-bar/WindowButtonsWeb.svelte";
import WindowButtonsWindows from "@graphite/components/window/title-bar/WindowButtonsWindows.svelte";
@ -17,25 +20,73 @@
export let platform: Platform;
export let maximized: boolean;
const editor = getContext<Editor>("editor");
const portfolio = getContext<PortfolioState>("portfolio");
// TODO: Apparently, Safari does not support the Keyboard.lock() API but does relax its authority over certain keyboard shortcuts in fullscreen mode, which we should take advantage of
const ACCEL_KEY = platformIsMac() ? "Command" : "Control";
const LOCK_REQUIRING_SHORTCUTS: KeyRaw[][] = [
[ACCEL_KEY, "KeyW"],
[ACCEL_KEY, "KeyN"],
[ACCEL_KEY, "Shift", "KeyN"],
[ACCEL_KEY, "KeyT"],
[ACCEL_KEY, "Shift", "KeyT"],
];
let entries: MenuListEntry[] = [];
$: docIndex = $portfolio.activeDocumentIndex;
$: displayName = $portfolio.documents[docIndex]?.displayName || "";
$: windowTitle = `${displayName}${displayName && " - "}Graphite`;
onMount(() => {
const arraysEqual = (a: KeyRaw[], b: KeyRaw[]): boolean => a.length === b.length && a.every((aValue, i) => aValue === b[i]);
const shortcutRequiresLock = (shortcut: LayoutKeysGroup): boolean => {
const shortcutKeys = shortcut.map((keyWithLabel) => keyWithLabel.key);
// If this shortcut matches any of the browser-reserved shortcuts
return LOCK_REQUIRING_SHORTCUTS.some((lockKeyCombo) => arraysEqual(shortcutKeys, lockKeyCombo));
};
editor.subscriptions.subscribeJsMessage(UpdateMenuBarLayout, (updateMenuBarLayout) => {
const menuBarEntryToMenuListEntry = (entry: MenuBarEntry): MenuListEntry => ({
// From `MenuEntryCommon`
...entry,
// Shared names with fields that need to be converted from the type used in `MenuBarEntry` to that of `MenuListEntry`
action: () => editor.instance.updateLayout(updateMenuBarLayout.layoutTarget, entry.action.widgetId, undefined),
children: entry.children ? entry.children.map((entries) => entries.map((entry) => menuBarEntryToMenuListEntry(entry))) : undefined,
// New fields in `MenuListEntry`
shortcutRequiresLock: entry.shortcut ? shortcutRequiresLock(entry.shortcut.keys) : undefined,
value: undefined,
disabled: entry.disabled ?? undefined,
font: undefined,
ref: undefined,
});
entries = updateMenuBarLayout.layout.map(menuBarEntryToMenuListEntry);
});
});
</script>
<LayoutRow class="title-bar">
<LayoutRow class="header-part">
<!-- Menu bar (or on Mac: window buttons) -->
<LayoutRow class="left">
{#if platform === "Mac"}
<WindowButtonsMac {maximized} />
{:else}
<MenuBarInput />
{#each entries as entry}
<MenuListButton {entry} />
{/each}
{/if}
</LayoutRow>
<LayoutRow class="header-part">
<!-- Document title -->
<LayoutRow class="center">
<WindowTitle text={windowTitle} />
</LayoutRow>
<LayoutRow class="header-part">
<!-- Window buttons (except on Mac) -->
<LayoutRow class="right">
{#if platform === "Windows" || platform === "Linux"}
<WindowButtonsWindows {maximized} />
{:else if platform === "Web"}
@ -49,18 +100,18 @@
height: 28px;
flex: 0 0 auto;
.header-part {
> .layout-row {
flex: 1 1 100%;
&:nth-child(1) {
&.left {
justify-content: flex-start;
}
&:nth-child(2) {
&.center {
justify-content: center;
}
&:nth-child(3) {
&.right {
justify-content: flex-end;
}
}

View File

@ -60,7 +60,7 @@
<LayoutCol class="panel">
<LayoutRow class="tab-bar" classes={{ "min-widths": tabMinWidths }}>
<LayoutRow class="tab-group" scrollableX={true}>
{#each tabLabels as tabLabel, tabIndex (tabIndex)}
{#each tabLabels as tabLabel, tabIndex}
<LayoutRow
class="tab"
classes={{ active: tabIndex === tabActiveIndex }}

View File

@ -7,7 +7,7 @@
import type { FrontendDocumentDetails } from "@graphite/wasm-communication/messages";
import DialogModal from "@graphite/components/floating-menus/DialogModal.svelte";
import Dialog from "@graphite/components/floating-menus/Dialog.svelte";
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
import Panel from "@graphite/components/window/workspace/Panel.svelte";
@ -124,7 +124,7 @@
</LayoutCol>
</LayoutRow>
{#if $dialog.visible}
<DialogModal />
<Dialog />
{/if}
</LayoutRow>

View File

@ -60,7 +60,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
// Keyboard events
async function shouldRedirectKeyboardEventToBackend(e: KeyboardEvent): Promise<boolean> {
// Don't redirect when a modal is covering the workspace
// Don't redirect when a dialog is covering the workspace
if (get(dialog).visible) return false;
const key = await getLocalizedScanCode(e);
@ -157,7 +157,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
function onPointerDown(e: PointerEvent) {
const { target } = e;
const isTargetingCanvas = target instanceof Element && target.closest("[data-viewport]");
const inDialog = target instanceof Element && target.closest("[data-dialog-modal] [data-floating-menu-content]");
const inDialog = target instanceof Element && target.closest("[data-dialog] [data-floating-menu-content]");
const inTextInput = target === textToolInteractiveInputElement;
if (get(dialog).visible && !inDialog) {

View File

@ -6,6 +6,8 @@ import { Transform, Type, plainToClass } from "class-transformer";
import { type IconName, type IconSize } from "@graphite/utility-functions/icons";
import { type WasmEditorInstance, type WasmRawInstance } from "@graphite/wasm-communication/editor";
import type MenuList from "@graphite/components/floating-menus/MenuList.svelte";
export class JsMessage {
// The marker provides a way to check if an object is a sub-class constructor for a jsMessage.
static readonly jsMessageMarker = true;
@ -791,7 +793,7 @@ export type MenuBarEntry = MenuEntryCommon & {
disabled?: boolean;
};
// An entry in the all-encompassing MenuList component which defines all types of menus ranging from `MenuBarInput` to `DropdownInput` widgets
// An entry in the all-encompassing MenuList component which defines all types of menus (which are spawned by widgets like `MenuListButton` and `DropdownInput`)
export type MenuListEntry = MenuEntryCommon & {
action?: () => void;
children?: MenuListEntry[][];
@ -801,7 +803,7 @@ export type MenuListEntry = MenuEntryCommon & {
disabled?: boolean;
tooltip?: string;
font?: URL;
ref?: any;
ref?: MenuList;
};
export class CurveManipulatorGroup {
@ -1010,7 +1012,7 @@ export class Separator extends WidgetProps {
type!: SeparatorType;
}
export class SwatchPairInput extends WidgetProps {
export class WorkingColorsButton extends WidgetProps {
@Type(() => Color)
primary!: Color;
@ -1119,7 +1121,7 @@ export class TextLabel extends WidgetProps {
export type PivotPosition = "None" | "TopLeft" | "TopCenter" | "TopRight" | "CenterLeft" | "Center" | "CenterRight" | "BottomLeft" | "BottomCenter" | "BottomRight";
export class PivotAssist extends WidgetProps {
export class PivotInput extends WidgetProps {
position!: PivotPosition;
disabled!: boolean;
@ -1141,11 +1143,11 @@ const widgetSubTypes = [
{ value: NumberInput, name: "NumberInput" },
{ value: OptionalInput, name: "OptionalInput" },
{ value: ParameterExposeButton, name: "ParameterExposeButton" },
{ value: PivotAssist, name: "PivotAssist" },
{ value: PivotInput, name: "PivotInput" },
{ value: PopoverButton, name: "PopoverButton" },
{ value: RadioInput, name: "RadioInput" },
{ value: Separator, name: "Separator" },
{ value: SwatchPairInput, name: "SwatchPairInput" },
{ value: WorkingColorsButton, name: "WorkingColorsButton" },
{ value: TextAreaInput, name: "TextAreaInput" },
{ value: TextButton, name: "TextButton" },
{ value: TextInput, name: "TextInput" },
@ -1259,16 +1261,16 @@ export function patchWidgetLayout(layout: /* &mut */ WidgetLayout, updates: Widg
});
}
export type LayoutGroup = WidgetRow | WidgetColumn | WidgetSection;
export type LayoutGroup = WidgetSpanRow | WidgetSpanColumn | WidgetSection;
export type WidgetColumn = { columnWidgets: Widget[] };
export function isWidgetColumn(layoutColumn: LayoutGroup): layoutColumn is WidgetColumn {
return Boolean((layoutColumn as WidgetColumn).columnWidgets);
export type WidgetSpanColumn = { columnWidgets: Widget[] };
export function isWidgetSpanColumn(layoutColumn: LayoutGroup): layoutColumn is WidgetSpanColumn {
return Boolean((layoutColumn as WidgetSpanColumn).columnWidgets);
}
export type WidgetRow = { rowWidgets: Widget[] };
export function isWidgetRow(layoutRow: LayoutGroup): layoutRow is WidgetRow {
return Boolean((layoutRow as WidgetRow).rowWidgets);
export type WidgetSpanRow = { rowWidgets: Widget[] };
export function isWidgetSpanRow(layoutRow: LayoutGroup): layoutRow is WidgetSpanRow {
return Boolean((layoutRow as WidgetSpanRow).rowWidgets);
}
export type WidgetSection = { name: string; layout: LayoutGroup[] };
@ -1301,12 +1303,12 @@ function createLayoutGroup(layoutGroup: any): LayoutGroup {
if (layoutGroup.column) {
const columnWidgets = hoistWidgetHolders(layoutGroup.column.columnWidgets);
const result: WidgetColumn = { columnWidgets };
const result: WidgetSpanColumn = { columnWidgets };
return result;
}
if (layoutGroup.row) {
const result: WidgetRow = { rowWidgets: hoistWidgetHolders(layoutGroup.row.rowWidgets) };
const result: WidgetSpanRow = { rowWidgets: hoistWidgetHolders(layoutGroup.row.rowWidgets) };
return result;
}

View File

@ -10,7 +10,7 @@ order = 1 # Page number after chapter intro
- Interface
- Title bar
- Menu bar
- Focused document title
- Document title
- Window buttons
- Workspace
- Panel interface (tab, pin, options bar, left menu)