mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-04 13:30:48 +00:00
Tidy up the Layers panel with a new bottom bar
This commit is contained in:
parent
66a297df2c
commit
899ed5ad85
23 changed files with 286 additions and 92 deletions
|
@ -240,7 +240,17 @@ pub enum FrontendMessage {
|
||||||
#[serde(rename = "hintData")]
|
#[serde(rename = "hintData")]
|
||||||
hint_data: HintData,
|
hint_data: HintData,
|
||||||
},
|
},
|
||||||
UpdateLayersPanelControlBarLayout {
|
UpdateLayersPanelControlBarLeftLayout {
|
||||||
|
#[serde(rename = "layoutTarget")]
|
||||||
|
layout_target: LayoutTarget,
|
||||||
|
diff: Vec<WidgetDiff>,
|
||||||
|
},
|
||||||
|
UpdateLayersPanelControlBarRightLayout {
|
||||||
|
#[serde(rename = "layoutTarget")]
|
||||||
|
layout_target: LayoutTarget,
|
||||||
|
diff: Vec<WidgetDiff>,
|
||||||
|
},
|
||||||
|
UpdateLayersPanelBottomBarLayout {
|
||||||
#[serde(rename = "layoutTarget")]
|
#[serde(rename = "layoutTarget")]
|
||||||
layout_target: LayoutTarget,
|
layout_target: LayoutTarget,
|
||||||
diff: Vec<WidgetDiff>,
|
diff: Vec<WidgetDiff>,
|
||||||
|
|
|
@ -424,7 +424,9 @@ impl LayoutMessageHandler {
|
||||||
LayoutTarget::DialogColumn2 => FrontendMessage::UpdateDialogColumn2 { layout_target, diff },
|
LayoutTarget::DialogColumn2 => FrontendMessage::UpdateDialogColumn2 { layout_target, diff },
|
||||||
LayoutTarget::DocumentBar => FrontendMessage::UpdateDocumentBarLayout { layout_target, diff },
|
LayoutTarget::DocumentBar => FrontendMessage::UpdateDocumentBarLayout { layout_target, diff },
|
||||||
LayoutTarget::DocumentMode => FrontendMessage::UpdateDocumentModeLayout { layout_target, diff },
|
LayoutTarget::DocumentMode => FrontendMessage::UpdateDocumentModeLayout { layout_target, diff },
|
||||||
LayoutTarget::LayersPanelControlBar => FrontendMessage::UpdateLayersPanelControlBarLayout { layout_target, diff },
|
LayoutTarget::LayersPanelControlLeftBar => FrontendMessage::UpdateLayersPanelControlBarLeftLayout { layout_target, diff },
|
||||||
|
LayoutTarget::LayersPanelControlRightBar => FrontendMessage::UpdateLayersPanelControlBarRightLayout { layout_target, diff },
|
||||||
|
LayoutTarget::LayersPanelBottomBar => FrontendMessage::UpdateLayersPanelBottomBarLayout { layout_target, diff },
|
||||||
LayoutTarget::MenuBar => unreachable!("Menu bar is not diffed"),
|
LayoutTarget::MenuBar => unreachable!("Menu bar is not diffed"),
|
||||||
LayoutTarget::NodeGraphControlBar => FrontendMessage::UpdateNodeGraphControlBarLayout { layout_target, diff },
|
LayoutTarget::NodeGraphControlBar => FrontendMessage::UpdateNodeGraphControlBarLayout { layout_target, diff },
|
||||||
LayoutTarget::PropertiesSections => FrontendMessage::UpdatePropertyPanelSectionsLayout { layout_target, diff },
|
LayoutTarget::PropertiesSections => FrontendMessage::UpdatePropertyPanelSectionsLayout { layout_target, diff },
|
||||||
|
|
|
@ -31,8 +31,12 @@ pub enum LayoutTarget {
|
||||||
DocumentBar,
|
DocumentBar,
|
||||||
/// Contains the dropdown for design / select / guide mode found on the top left of the canvas.
|
/// Contains the dropdown for design / select / guide mode found on the top left of the canvas.
|
||||||
DocumentMode,
|
DocumentMode,
|
||||||
/// Options for opacity seen at the top of the Layers panel.
|
/// Blending options at the top of the Layers panel.
|
||||||
LayersPanelControlBar,
|
LayersPanelControlLeftBar,
|
||||||
|
/// Selected layer status (locked/hidden) at the top of the Layers panel.
|
||||||
|
LayersPanelControlRightBar,
|
||||||
|
/// Controls for adding, grouping, and deleting layers at the bottom of the Layers panel.
|
||||||
|
LayersPanelBottomBar,
|
||||||
/// The dropdown menu at the very top of the application: File, Edit, etc.
|
/// The dropdown menu at the very top of the application: File, Edit, etc.
|
||||||
MenuBar,
|
MenuBar,
|
||||||
/// Bar at the top of the node graph containing the location and the "Preview" and "Hide" buttons.
|
/// Bar at the top of the node graph containing the location and the "Preview" and "Hide" buttons.
|
||||||
|
|
|
@ -42,6 +42,9 @@ pub struct IconButton {
|
||||||
pub struct PopoverButton {
|
pub struct PopoverButton {
|
||||||
pub style: Option<String>,
|
pub style: Option<String>,
|
||||||
|
|
||||||
|
#[serde(rename = "menuDirection")]
|
||||||
|
pub menu_direction: Option<MenuDirection>,
|
||||||
|
|
||||||
pub icon: Option<String>,
|
pub icon: Option<String>,
|
||||||
|
|
||||||
pub disabled: bool,
|
pub disabled: bool,
|
||||||
|
@ -58,6 +61,20 @@ pub struct PopoverButton {
|
||||||
pub popover_min_width: Option<u32>,
|
pub popover_min_width: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||||
|
pub enum MenuDirection {
|
||||||
|
Top,
|
||||||
|
#[default]
|
||||||
|
Bottom,
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
TopLeft,
|
||||||
|
TopRight,
|
||||||
|
BottomLeft,
|
||||||
|
BottomRight,
|
||||||
|
Center,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder, specta::Type)]
|
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder, specta::Type)]
|
||||||
#[derivative(Debug, PartialEq)]
|
#[derivative(Debug, PartialEq)]
|
||||||
pub struct ParameterExposeButton {
|
pub struct ParameterExposeButton {
|
||||||
|
|
|
@ -67,6 +67,13 @@ pub struct DropdownInput {
|
||||||
|
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub tooltip_shortcut: Option<ActionKeys>,
|
pub tooltip_shortcut: Option<ActionKeys>,
|
||||||
|
|
||||||
|
// Styling
|
||||||
|
#[serde(rename = "minWidth")]
|
||||||
|
pub min_width: u32,
|
||||||
|
|
||||||
|
#[serde(rename = "maxWidth")]
|
||||||
|
pub max_width: u32,
|
||||||
//
|
//
|
||||||
// Callbacks
|
// Callbacks
|
||||||
// `on_update` exists on the `MenuListEntry`, not this parent `DropdownInput`
|
// `on_update` exists on the `MenuListEntry`, not this parent `DropdownInput`
|
||||||
|
@ -208,6 +215,9 @@ pub struct NumberInput {
|
||||||
#[serde(rename = "minWidth")]
|
#[serde(rename = "minWidth")]
|
||||||
pub min_width: u32,
|
pub min_width: u32,
|
||||||
|
|
||||||
|
#[serde(rename = "maxWidth")]
|
||||||
|
pub max_width: u32,
|
||||||
|
|
||||||
// Callbacks
|
// Callbacks
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||||
|
|
|
@ -299,7 +299,17 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||||
// Clear the control bar
|
// Clear the control bar
|
||||||
responses.add(LayoutMessage::SendLayout {
|
responses.add(LayoutMessage::SendLayout {
|
||||||
layout: Layout::WidgetLayout(Default::default()),
|
layout: Layout::WidgetLayout(Default::default()),
|
||||||
layout_target: LayoutTarget::LayersPanelControlBar,
|
layout_target: LayoutTarget::LayersPanelControlLeftBar,
|
||||||
|
});
|
||||||
|
responses.add(LayoutMessage::SendLayout {
|
||||||
|
layout: Layout::WidgetLayout(Default::default()),
|
||||||
|
layout_target: LayoutTarget::LayersPanelControlRightBar,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clear the bottom bar
|
||||||
|
responses.add(LayoutMessage::SendLayout {
|
||||||
|
layout: Layout::WidgetLayout(Default::default()),
|
||||||
|
layout_target: LayoutTarget::LayersPanelBottomBar,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
DocumentMessage::CreateEmptyFolder => {
|
DocumentMessage::CreateEmptyFolder => {
|
||||||
|
@ -344,6 +354,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||||
DocumentMessage::DocumentHistoryForward => self.redo_with_history(ipp, responses),
|
DocumentMessage::DocumentHistoryForward => self.redo_with_history(ipp, responses),
|
||||||
DocumentMessage::DocumentStructureChanged => {
|
DocumentMessage::DocumentStructureChanged => {
|
||||||
self.update_layers_panel_control_bar_widgets(responses);
|
self.update_layers_panel_control_bar_widgets(responses);
|
||||||
|
self.update_layers_panel_bottom_bar_widgets(responses);
|
||||||
|
|
||||||
self.network_interface.load_structure();
|
self.network_interface.load_structure();
|
||||||
let data_buffer: RawBuffer = self.serialize_root();
|
let data_buffer: RawBuffer = self.serialize_root();
|
||||||
|
@ -2509,12 +2520,14 @@ impl DocumentMessageHandler {
|
||||||
.selected_index(blend_mode.and_then(|blend_mode| blend_mode.index_in_list_svg_subset()).map(|index| index as u32))
|
.selected_index(blend_mode.and_then(|blend_mode| blend_mode.index_in_list_svg_subset()).map(|index| index as u32))
|
||||||
.disabled(disabled)
|
.disabled(disabled)
|
||||||
.draw_icon(false)
|
.draw_icon(false)
|
||||||
|
.max_width(100)
|
||||||
|
.tooltip("Blend Mode")
|
||||||
.widget_holder(),
|
.widget_holder(),
|
||||||
Separator::new(SeparatorType::Related).widget_holder(),
|
Separator::new(SeparatorType::Related).widget_holder(),
|
||||||
NumberInput::new(opacity)
|
NumberInput::new(opacity)
|
||||||
.label("Opacity")
|
.label("Opacity")
|
||||||
.unit("%")
|
.unit("%")
|
||||||
.display_decimal_places(2)
|
.display_decimal_places(0)
|
||||||
.disabled(disabled)
|
.disabled(disabled)
|
||||||
.min(0.)
|
.min(0.)
|
||||||
.max(100.)
|
.max(100.)
|
||||||
|
@ -2529,33 +2542,13 @@ impl DocumentMessageHandler {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.on_commit(|_| DocumentMessage::AddTransaction.into())
|
.on_commit(|_| DocumentMessage::AddTransaction.into())
|
||||||
|
.max_width(100)
|
||||||
|
.tooltip("Opacity")
|
||||||
.widget_holder(),
|
.widget_holder(),
|
||||||
//
|
];
|
||||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
let layers_panel_control_bar_left = WidgetLayout::new(vec![LayoutGroup::Row { widgets }]);
|
||||||
//
|
|
||||||
IconButton::new("NewLayer", 24)
|
let widgets = vec![
|
||||||
.tooltip("New Layer")
|
|
||||||
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::CreateEmptyFolder))
|
|
||||||
.on_update(|_| DocumentMessage::CreateEmptyFolder.into())
|
|
||||||
.widget_holder(),
|
|
||||||
IconButton::new("Folder", 24)
|
|
||||||
.tooltip("Group Selected")
|
|
||||||
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::GroupSelectedLayers))
|
|
||||||
.on_update(|_| {
|
|
||||||
let group_folder_type = GroupFolderType::Layer;
|
|
||||||
DocumentMessage::GroupSelectedLayers { group_folder_type }.into()
|
|
||||||
})
|
|
||||||
.disabled(!has_selection)
|
|
||||||
.widget_holder(),
|
|
||||||
IconButton::new("Trash", 24)
|
|
||||||
.tooltip("Delete Selected")
|
|
||||||
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::DeleteSelectedLayers))
|
|
||||||
.on_update(|_| DocumentMessage::DeleteSelectedLayers.into())
|
|
||||||
.disabled(!has_selection)
|
|
||||||
.widget_holder(),
|
|
||||||
//
|
|
||||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
|
||||||
//
|
|
||||||
IconButton::new(if selection_all_locked { "PadlockLocked" } else { "PadlockUnlocked" }, 24)
|
IconButton::new(if selection_all_locked { "PadlockLocked" } else { "PadlockUnlocked" }, 24)
|
||||||
.hover_icon(Some((if selection_all_locked { "PadlockUnlocked" } else { "PadlockLocked" }).into()))
|
.hover_icon(Some((if selection_all_locked { "PadlockUnlocked" } else { "PadlockLocked" }).into()))
|
||||||
.tooltip(if selection_all_locked { "Unlock Selected" } else { "Lock Selected" })
|
.tooltip(if selection_all_locked { "Unlock Selected" } else { "Lock Selected" })
|
||||||
|
@ -2571,11 +2564,75 @@ impl DocumentMessageHandler {
|
||||||
.disabled(!has_selection)
|
.disabled(!has_selection)
|
||||||
.widget_holder(),
|
.widget_holder(),
|
||||||
];
|
];
|
||||||
let layers_panel_control_bar = WidgetLayout::new(vec![LayoutGroup::Row { widgets }]);
|
let layers_panel_control_bar_right = WidgetLayout::new(vec![LayoutGroup::Row { widgets }]);
|
||||||
|
|
||||||
responses.add(LayoutMessage::SendLayout {
|
responses.add(LayoutMessage::SendLayout {
|
||||||
layout: Layout::WidgetLayout(layers_panel_control_bar),
|
layout: Layout::WidgetLayout(layers_panel_control_bar_left),
|
||||||
layout_target: LayoutTarget::LayersPanelControlBar,
|
layout_target: LayoutTarget::LayersPanelControlLeftBar,
|
||||||
|
});
|
||||||
|
responses.add(LayoutMessage::SendLayout {
|
||||||
|
layout: Layout::WidgetLayout(layers_panel_control_bar_right),
|
||||||
|
layout_target: LayoutTarget::LayersPanelControlRightBar,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_layers_panel_bottom_bar_widgets(&self, responses: &mut VecDeque<Message>) {
|
||||||
|
let selected_nodes = self.network_interface.selected_nodes();
|
||||||
|
let mut selected_layers = selected_nodes.selected_layers(self.metadata());
|
||||||
|
let selected_layer = selected_layers.next();
|
||||||
|
let has_selection = selected_layer.is_some();
|
||||||
|
let has_multiple_selection = selected_layers.next().is_some();
|
||||||
|
|
||||||
|
let widgets = vec![
|
||||||
|
PopoverButton::new()
|
||||||
|
.icon(Some("Node".to_string()))
|
||||||
|
.menu_direction(Some(MenuDirection::Top))
|
||||||
|
.tooltip("Add an operation to the end of this layer's chain of nodes")
|
||||||
|
.disabled(!has_selection || has_multiple_selection)
|
||||||
|
.popover_layout({
|
||||||
|
let node_chooser = NodeCatalog::new()
|
||||||
|
.on_update(move |node_type| {
|
||||||
|
if let Some(layer) = selected_layer {
|
||||||
|
NodeGraphMessage::CreateNodeInLayerWithTransaction {
|
||||||
|
node_type: node_type.clone(),
|
||||||
|
layer: LayerNodeIdentifier::new_unchecked(layer.to_node()),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
} else {
|
||||||
|
Message::NoOp
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.widget_holder();
|
||||||
|
vec![LayoutGroup::Row { widgets: vec![node_chooser] }]
|
||||||
|
})
|
||||||
|
.widget_holder(),
|
||||||
|
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||||
|
IconButton::new("Folder", 24)
|
||||||
|
.tooltip("Group Selected")
|
||||||
|
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::GroupSelectedLayers))
|
||||||
|
.on_update(|_| {
|
||||||
|
let group_folder_type = GroupFolderType::Layer;
|
||||||
|
DocumentMessage::GroupSelectedLayers { group_folder_type }.into()
|
||||||
|
})
|
||||||
|
.disabled(!has_selection)
|
||||||
|
.widget_holder(),
|
||||||
|
IconButton::new("NewLayer", 24)
|
||||||
|
.tooltip("New Layer")
|
||||||
|
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::CreateEmptyFolder))
|
||||||
|
.on_update(|_| DocumentMessage::CreateEmptyFolder.into())
|
||||||
|
.widget_holder(),
|
||||||
|
IconButton::new("Trash", 24)
|
||||||
|
.tooltip("Delete Selected")
|
||||||
|
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::DeleteSelectedLayers))
|
||||||
|
.on_update(|_| DocumentMessage::DeleteSelectedLayers.into())
|
||||||
|
.disabled(!has_selection)
|
||||||
|
.widget_holder(),
|
||||||
|
];
|
||||||
|
let layers_panel_bottom_bar = WidgetLayout::new(vec![LayoutGroup::Row { widgets }]);
|
||||||
|
|
||||||
|
responses.add(LayoutMessage::SendLayout {
|
||||||
|
layout: Layout::WidgetLayout(layers_panel_bottom_bar),
|
||||||
|
layout_target: LayoutTarget::LayersPanelBottomBar,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2009,12 +2009,38 @@ impl NodeGraphMessageHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next, we decide what to display based on the number of layers and nodes selected
|
// Next, we decide what to display based on the number of layers and nodes selected
|
||||||
match layers.len() {
|
match *layers.as_slice() {
|
||||||
// If no layers are selected, show properties for all selected nodes
|
// If no layers are selected, show properties for all selected nodes
|
||||||
0 => {
|
[] => {
|
||||||
let selected_nodes = nodes.iter().map(|node_id| node_properties::generate_node_properties(*node_id, context)).collect::<Vec<_>>();
|
let selected_nodes = nodes.iter().map(|node_id| node_properties::generate_node_properties(*node_id, context)).collect::<Vec<_>>();
|
||||||
if !selected_nodes.is_empty() {
|
if !selected_nodes.is_empty() {
|
||||||
return selected_nodes;
|
let mut properties = Vec::new();
|
||||||
|
|
||||||
|
if let [node_id] = *nodes.as_slice() {
|
||||||
|
properties.push(LayoutGroup::Row {
|
||||||
|
widgets: vec![
|
||||||
|
Separator::new(SeparatorType::Related).widget_holder(),
|
||||||
|
IconLabel::new("Node").tooltip("Name of the selected node").widget_holder(),
|
||||||
|
Separator::new(SeparatorType::Related).widget_holder(),
|
||||||
|
TextInput::new(context.network_interface.display_name(&node_id, context.selection_network_path))
|
||||||
|
.tooltip("Name of the selected node")
|
||||||
|
.on_update(move |text_input| {
|
||||||
|
NodeGraphMessage::SetDisplayName {
|
||||||
|
node_id,
|
||||||
|
alias: text_input.value.clone(),
|
||||||
|
skip_adding_history_step: false,
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
})
|
||||||
|
.widget_holder(),
|
||||||
|
Separator::new(SeparatorType::Related).widget_holder(),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
properties.extend(selected_nodes);
|
||||||
|
|
||||||
|
return properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Display properties for encapsulating node when no nodes are selected in a nested network
|
// TODO: Display properties for encapsulating node when no nodes are selected in a nested network
|
||||||
|
@ -2056,8 +2082,7 @@ impl NodeGraphMessageHandler {
|
||||||
properties
|
properties
|
||||||
}
|
}
|
||||||
// If one layer is selected, filter out all selected nodes that are not upstream of it. If there are no nodes left, show properties for the layer. Otherwise, show nothing.
|
// If one layer is selected, filter out all selected nodes that are not upstream of it. If there are no nodes left, show properties for the layer. Otherwise, show nothing.
|
||||||
1 => {
|
[layer] => {
|
||||||
let layer = layers[0];
|
|
||||||
let nodes_not_upstream_of_layer = nodes.into_iter().filter(|&selected_node_id| {
|
let nodes_not_upstream_of_layer = nodes.into_iter().filter(|&selected_node_id| {
|
||||||
!context
|
!context
|
||||||
.network_interface
|
.network_interface
|
||||||
|
|
|
@ -1431,6 +1431,7 @@ impl PortfolioMessageHandler {
|
||||||
self.document_ids.push_back(document_id);
|
self.document_ids.push_back(document_id);
|
||||||
}
|
}
|
||||||
new_document.update_layers_panel_control_bar_widgets(responses);
|
new_document.update_layers_panel_control_bar_widgets(responses);
|
||||||
|
new_document.update_layers_panel_bottom_bar_widgets(responses);
|
||||||
|
|
||||||
self.documents.insert(document_id, new_document);
|
self.documents.insert(document_id, new_document);
|
||||||
|
|
||||||
|
|
3
frontend/assets/icon-12px-solid/clipped.svg
Normal file
3
frontend/assets/icon-12px-solid/clipped.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12">
|
||||||
|
<path d="M4.5,4H10V3H4.5C3.673,3,3,3.673,3,4.5V7H1l2.5,3L6,7H4V4.5C4,4.224,4.224,4,4.5,4z" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 163 B |
|
@ -3,10 +3,11 @@
|
||||||
|
|
||||||
import type { Editor } from "@graphite/editor";
|
import type { Editor } from "@graphite/editor";
|
||||||
import type { HSV, RGB, FillChoice } from "@graphite/messages";
|
import type { HSV, RGB, FillChoice } from "@graphite/messages";
|
||||||
|
import type { MenuDirection } from "@graphite/messages";
|
||||||
import { Color, contrastingOutlineFactor, Gradient } from "@graphite/messages";
|
import { Color, contrastingOutlineFactor, Gradient } from "@graphite/messages";
|
||||||
import { clamp } from "@graphite/utility-functions/math";
|
import { clamp } from "@graphite/utility-functions/math";
|
||||||
|
|
||||||
import FloatingMenu, { type MenuDirection } from "@graphite/components/layout/FloatingMenu.svelte";
|
import FloatingMenu from "@graphite/components/layout/FloatingMenu.svelte";
|
||||||
import { preventEscapeClosingParentFloatingMenu } from "@graphite/components/layout/FloatingMenu.svelte";
|
import { preventEscapeClosingParentFloatingMenu } from "@graphite/components/layout/FloatingMenu.svelte";
|
||||||
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||||
|
|
|
@ -3,10 +3,10 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher, tick, onDestroy, onMount } from "svelte";
|
import { createEventDispatcher, tick, onDestroy, onMount } from "svelte";
|
||||||
|
|
||||||
import type { MenuListEntry } from "@graphite/messages";
|
import type { MenuListEntry, MenuDirection } from "@graphite/messages";
|
||||||
|
|
||||||
import MenuList from "@graphite/components/floating-menus/MenuList.svelte";
|
import MenuList from "@graphite/components/floating-menus/MenuList.svelte";
|
||||||
import FloatingMenu, { type MenuDirection } from "@graphite/components/layout/FloatingMenu.svelte";
|
import FloatingMenu from "@graphite/components/layout/FloatingMenu.svelte";
|
||||||
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||||
import TextInput from "@graphite/components/widgets/inputs/TextInput.svelte";
|
import TextInput from "@graphite/components/widgets/inputs/TextInput.svelte";
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<script lang="ts" context="module">
|
<script lang="ts" context="module">
|
||||||
export type MenuDirection = "Top" | "Bottom" | "Left" | "Right" | "TopLeft" | "TopRight" | "BottomLeft" | "BottomRight" | "Center";
|
|
||||||
export type MenuType = "Popover" | "Dropdown" | "Dialog" | "Cursor";
|
export type MenuType = "Popover" | "Dropdown" | "Dialog" | "Cursor";
|
||||||
|
|
||||||
/// Prevents the escape key from closing the parent floating menu of the given element.
|
/// Prevents the escape key from closing the parent floating menu of the given element.
|
||||||
|
@ -22,6 +21,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount, afterUpdate, createEventDispatcher, tick } from "svelte";
|
import { onMount, afterUpdate, createEventDispatcher, tick } from "svelte";
|
||||||
|
|
||||||
|
import type { MenuDirection } from "@graphite/messages";
|
||||||
import { browserVersion } from "@graphite/utility-functions/platform";
|
import { browserVersion } from "@graphite/utility-functions/platform";
|
||||||
|
|
||||||
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||||
|
@ -184,6 +184,7 @@
|
||||||
const floatingMenuContentDiv = floatingMenuContent?.div?.();
|
const floatingMenuContentDiv = floatingMenuContent?.div?.();
|
||||||
if (!workspace || !self || !floatingMenuContainer || !floatingMenuContent || !floatingMenuContentDiv) return;
|
if (!workspace || !self || !floatingMenuContainer || !floatingMenuContent || !floatingMenuContentDiv) return;
|
||||||
|
|
||||||
|
const viewportBounds = document.documentElement.getBoundingClientRect();
|
||||||
workspaceBounds = workspace.getBoundingClientRect();
|
workspaceBounds = workspace.getBoundingClientRect();
|
||||||
floatingMenuBounds = self.getBoundingClientRect();
|
floatingMenuBounds = self.getBoundingClientRect();
|
||||||
const floatingMenuContainerBounds = floatingMenuContainer.getBoundingClientRect();
|
const floatingMenuContainerBounds = floatingMenuContainer.getBoundingClientRect();
|
||||||
|
@ -195,17 +196,17 @@
|
||||||
// Required to correctly position content when scrolled (it has a `position: fixed` to prevent clipping)
|
// Required to correctly position content when scrolled (it has a `position: fixed` to prevent clipping)
|
||||||
// We use `.style` on a div (instead of a style DOM attribute binding) because the binding causes the `afterUpdate()` hook to call the function we're in recursively forever
|
// We use `.style` on a div (instead of a style DOM attribute binding) because the binding causes the `afterUpdate()` hook to call the function we're in recursively forever
|
||||||
const tailOffset = type === "Popover" ? 10 : 0;
|
const tailOffset = type === "Popover" ? 10 : 0;
|
||||||
if (direction === "Bottom") floatingMenuContentDiv.style.top = `${tailOffset + floatingMenuBounds.top}px`;
|
if (direction === "Bottom") floatingMenuContentDiv.style.top = `${tailOffset + floatingMenuBounds.y}px`;
|
||||||
if (direction === "Top") floatingMenuContentDiv.style.bottom = `${tailOffset + floatingMenuBounds.bottom}px`;
|
if (direction === "Top") floatingMenuContentDiv.style.bottom = `${tailOffset + (viewportBounds.height - floatingMenuBounds.y)}px`;
|
||||||
if (direction === "Right") floatingMenuContentDiv.style.left = `${tailOffset + floatingMenuBounds.left}px`;
|
if (direction === "Right") floatingMenuContentDiv.style.left = `${tailOffset + floatingMenuBounds.x}px`;
|
||||||
if (direction === "Left") floatingMenuContentDiv.style.right = `${tailOffset + floatingMenuBounds.right}px`;
|
if (direction === "Left") floatingMenuContentDiv.style.right = `${tailOffset + (viewportBounds.width - floatingMenuBounds.x)}px`;
|
||||||
|
|
||||||
// Required to correctly position tail when scrolled (it has a `position: fixed` to prevent clipping)
|
// Required to correctly position tail when scrolled (it has a `position: fixed` to prevent clipping)
|
||||||
// We use `.style` on a div (instead of a style DOM attribute binding) because the binding causes the `afterUpdate()` hook to call the function we're in recursively forever
|
// We use `.style` on a div (instead of a style DOM attribute binding) because the binding causes the `afterUpdate()` hook to call the function we're in recursively forever
|
||||||
if (tail && direction === "Bottom") tail.style.top = `${floatingMenuBounds.top}px`;
|
if (tail && direction === "Bottom") tail.style.top = `${floatingMenuBounds.y}px`;
|
||||||
if (tail && direction === "Top") tail.style.bottom = `${floatingMenuBounds.bottom}px`;
|
if (tail && direction === "Top") tail.style.bottom = `${viewportBounds.height - floatingMenuBounds.y}px`;
|
||||||
if (tail && direction === "Right") tail.style.left = `${floatingMenuBounds.left}px`;
|
if (tail && direction === "Right") tail.style.left = `${floatingMenuBounds.x}px`;
|
||||||
if (tail && direction === "Left") tail.style.right = `${floatingMenuBounds.right}px`;
|
if (tail && direction === "Left") tail.style.right = `${viewportBounds.width - floatingMenuBounds.x}px`;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Edge = "Top" | "Bottom" | "Left" | "Right";
|
type Edge = "Top" | "Bottom" | "Left" | "Right";
|
||||||
|
|
|
@ -3,7 +3,15 @@
|
||||||
|
|
||||||
import type { Editor } from "@graphite/editor";
|
import type { Editor } from "@graphite/editor";
|
||||||
import { beginDraggingElement } from "@graphite/io-managers/drag";
|
import { beginDraggingElement } from "@graphite/io-managers/drag";
|
||||||
import { defaultWidgetLayout, patchWidgetLayout, UpdateDocumentLayerDetails, UpdateDocumentLayerStructureJs, UpdateLayersPanelControlBarLayout } from "@graphite/messages";
|
import {
|
||||||
|
defaultWidgetLayout,
|
||||||
|
patchWidgetLayout,
|
||||||
|
UpdateDocumentLayerDetails,
|
||||||
|
UpdateDocumentLayerStructureJs,
|
||||||
|
UpdateLayersPanelControlBarLeftLayout,
|
||||||
|
UpdateLayersPanelControlBarRightLayout,
|
||||||
|
UpdateLayersPanelBottomBarLayout,
|
||||||
|
} from "@graphite/messages";
|
||||||
import type { DataBuffer, LayerPanelEntry } from "@graphite/messages";
|
import type { DataBuffer, LayerPanelEntry } from "@graphite/messages";
|
||||||
import type { NodeGraphState } from "@graphite/state-providers/node-graph";
|
import type { NodeGraphState } from "@graphite/state-providers/node-graph";
|
||||||
import { platformIsMac } from "@graphite/utility-functions/platform";
|
import { platformIsMac } from "@graphite/utility-functions/platform";
|
||||||
|
@ -13,6 +21,7 @@
|
||||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||||
import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte";
|
import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte";
|
||||||
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
|
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
|
||||||
|
import Separator from "@graphite/components/widgets/labels/Separator.svelte";
|
||||||
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
|
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
|
||||||
|
|
||||||
type LayerListingInfo = {
|
type LayerListingInfo = {
|
||||||
|
@ -47,12 +56,24 @@
|
||||||
let dragInPanel = false;
|
let dragInPanel = false;
|
||||||
|
|
||||||
// Layouts
|
// Layouts
|
||||||
let layersPanelControlBarLayout = defaultWidgetLayout();
|
let layersPanelControlBarLeftLayout = defaultWidgetLayout();
|
||||||
|
let layersPanelControlBarRightLayout = defaultWidgetLayout();
|
||||||
|
let layersPanelBottomBarLayout = defaultWidgetLayout();
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
editor.subscriptions.subscribeJsMessage(UpdateLayersPanelControlBarLayout, (updateLayersPanelControlBarLayout) => {
|
editor.subscriptions.subscribeJsMessage(UpdateLayersPanelControlBarLeftLayout, (updateLayersPanelControlBarLeftLayout) => {
|
||||||
patchWidgetLayout(layersPanelControlBarLayout, updateLayersPanelControlBarLayout);
|
patchWidgetLayout(layersPanelControlBarLeftLayout, updateLayersPanelControlBarLeftLayout);
|
||||||
layersPanelControlBarLayout = layersPanelControlBarLayout;
|
layersPanelControlBarLeftLayout = layersPanelControlBarLeftLayout;
|
||||||
|
});
|
||||||
|
|
||||||
|
editor.subscriptions.subscribeJsMessage(UpdateLayersPanelControlBarRightLayout, (updateLayersPanelControlBarRightLayout) => {
|
||||||
|
patchWidgetLayout(layersPanelControlBarRightLayout, updateLayersPanelControlBarRightLayout);
|
||||||
|
layersPanelControlBarRightLayout = layersPanelControlBarRightLayout;
|
||||||
|
});
|
||||||
|
|
||||||
|
editor.subscriptions.subscribeJsMessage(UpdateLayersPanelBottomBarLayout, (updateLayersPanelBottomBarLayout) => {
|
||||||
|
patchWidgetLayout(layersPanelBottomBarLayout, updateLayersPanelBottomBarLayout);
|
||||||
|
layersPanelBottomBarLayout = layersPanelBottomBarLayout;
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeJsMessage(UpdateDocumentLayerStructureJs, (updateDocumentLayerStructure) => {
|
editor.subscriptions.subscribeJsMessage(UpdateDocumentLayerStructureJs, (updateDocumentLayerStructure) => {
|
||||||
|
@ -407,7 +428,9 @@
|
||||||
|
|
||||||
<LayoutCol class="layers" on:dragleave={() => (dragInPanel = false)}>
|
<LayoutCol class="layers" on:dragleave={() => (dragInPanel = false)}>
|
||||||
<LayoutRow class="control-bar" scrollableX={true}>
|
<LayoutRow class="control-bar" scrollableX={true}>
|
||||||
<WidgetLayout layout={layersPanelControlBarLayout} />
|
<WidgetLayout layout={layersPanelControlBarLeftLayout} />
|
||||||
|
<Separator />
|
||||||
|
<WidgetLayout layout={layersPanelControlBarRightLayout} />
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
<LayoutRow class="list-area" scrollableY={true}>
|
<LayoutRow class="list-area" scrollableY={true}>
|
||||||
<LayoutCol class="list" data-layer-panel bind:this={list} on:click={() => deselectAllLayers()} on:dragover={updateInsertLine} on:dragend={drop} on:drop={drop}>
|
<LayoutCol class="list" data-layer-panel bind:this={list} on:click={() => deselectAllLayers()} on:dragover={updateInsertLine} on:dragend={drop} on:drop={drop}>
|
||||||
|
@ -490,6 +513,9 @@
|
||||||
<div class="insert-mark" style:left={`${4 + draggingData.insertDepth * 16}px`} style:top={`${draggingData.markerHeight}px`} />
|
<div class="insert-mark" style:left={`${4 + draggingData.insertDepth * 16}px`} style:top={`${draggingData.markerHeight}px`} />
|
||||||
{/if}
|
{/if}
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
|
<LayoutRow class="bottom-bar" scrollableX={true}>
|
||||||
|
<WidgetLayout layout={layersPanelBottomBarLayout} />
|
||||||
|
</LayoutRow>
|
||||||
</LayoutCol>
|
</LayoutCol>
|
||||||
|
|
||||||
<style lang="scss" global>
|
<style lang="scss" global>
|
||||||
|
@ -499,33 +525,25 @@
|
||||||
height: 32px;
|
height: 32px;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
margin: 0 4px;
|
margin: 0 4px;
|
||||||
|
border-bottom: 1px solid var(--color-2-mildblack);
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
.widget-span {
|
.widget-span:first-child {
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
min-width: 300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Blend mode selector and opacity slider
|
|
||||||
.dropdown-input,
|
|
||||||
.number-input {
|
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Blend mode selector
|
// Bottom bar
|
||||||
.dropdown-input {
|
.bottom-bar {
|
||||||
max-width: 120px;
|
height: 24px;
|
||||||
flex-basis: 120px;
|
padding-top: 4px;
|
||||||
}
|
flex: 0 0 auto;
|
||||||
|
margin: 0 4px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
border-top: 1px solid var(--color-2-mildblack);
|
||||||
|
|
||||||
// Opacity slider
|
.widget-span > * {
|
||||||
.number-input {
|
margin: 0;
|
||||||
max-width: 180px;
|
|
||||||
flex-basis: 180px;
|
|
||||||
|
|
||||||
+ .separator ~ .separator {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import type { MenuDirection } from "@graphite/messages";
|
||||||
import { type IconName, type PopoverButtonStyle } from "@graphite/utility-functions/icons";
|
import { type IconName, type PopoverButtonStyle } from "@graphite/utility-functions/icons";
|
||||||
|
|
||||||
import FloatingMenu from "@graphite/components/layout/FloatingMenu.svelte";
|
import FloatingMenu from "@graphite/components/layout/FloatingMenu.svelte";
|
||||||
|
@ -7,6 +8,7 @@
|
||||||
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
|
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
|
||||||
|
|
||||||
export let style: PopoverButtonStyle = "DropdownArrow";
|
export let style: PopoverButtonStyle = "DropdownArrow";
|
||||||
|
export let menuDirection: MenuDirection = "Bottom";
|
||||||
export let icon: IconName | undefined = undefined;
|
export let icon: IconName | undefined = undefined;
|
||||||
export let tooltip: string | undefined = undefined;
|
export let tooltip: string | undefined = undefined;
|
||||||
export let disabled = false;
|
export let disabled = false;
|
||||||
|
@ -23,13 +25,13 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<LayoutRow class="popover-button" classes={{ "has-icon": icon !== undefined }}>
|
<LayoutRow class="popover-button" classes={{ "has-icon": icon !== undefined, "direction-top": menuDirection === "Top" }}>
|
||||||
<IconButton class="dropdown-icon" classes={{ open }} {disabled} action={() => onClick()} icon={style || "DropdownArrow"} size={16} {tooltip} data-floating-menu-spawner />
|
<IconButton class="dropdown-icon" classes={{ open }} {disabled} action={() => onClick()} icon={style || "DropdownArrow"} size={16} {tooltip} data-floating-menu-spawner />
|
||||||
{#if icon !== undefined}
|
{#if icon !== undefined}
|
||||||
<IconLabel class="descriptive-icon" classes={{ open }} {disabled} {icon} {tooltip} />
|
<IconLabel class="descriptive-icon" classes={{ open }} {disabled} {icon} {tooltip} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<FloatingMenu {open} on:open={({ detail }) => (open = detail)} minWidth={popoverMinWidth} type="Popover" direction="Bottom">
|
<FloatingMenu {open} on:open={({ detail }) => (open = detail)} minWidth={popoverMinWidth} type="Popover" direction={menuDirection || "Bottom"}>
|
||||||
<slot />
|
<slot />
|
||||||
</FloatingMenu>
|
</FloatingMenu>
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
|
@ -48,6 +50,10 @@
|
||||||
padding-left: calc(36px - 16px);
|
padding-left: calc(36px - 16px);
|
||||||
box-sizing: content-box;
|
box-sizing: content-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.direction-top .dropdown-icon .icon-label {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-icon {
|
.dropdown-icon {
|
||||||
|
@ -86,5 +92,9 @@
|
||||||
margin-bottom: -8px;
|
margin-bottom: -8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.direction-top .floating-menu {
|
||||||
|
bottom: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -55,7 +55,7 @@
|
||||||
class:emphasized
|
class:emphasized
|
||||||
class:disabled
|
class:disabled
|
||||||
class:flush
|
class:flush
|
||||||
style:min-width={minWidth > 0 ? `${minWidth}px` : ""}
|
style:min-width={minWidth > 0 ? `${minWidth}px` : undefined}
|
||||||
title={tooltip}
|
title={tooltip}
|
||||||
data-emphasized={emphasized || undefined}
|
data-emphasized={emphasized || undefined}
|
||||||
data-disabled={disabled || undefined}
|
data-disabled={disabled || undefined}
|
||||||
|
|
|
@ -21,12 +21,13 @@
|
||||||
export let interactive = true;
|
export let interactive = true;
|
||||||
export let disabled = false;
|
export let disabled = false;
|
||||||
export let tooltip: string | undefined = undefined;
|
export let tooltip: string | undefined = undefined;
|
||||||
|
export let minWidth = 0;
|
||||||
|
export let maxWidth = 0;
|
||||||
|
|
||||||
let activeEntry = makeActiveEntry();
|
let activeEntry = makeActiveEntry();
|
||||||
let activeEntrySkipWatcher = false;
|
let activeEntrySkipWatcher = false;
|
||||||
let initialSelectedIndex: number | undefined = undefined;
|
let initialSelectedIndex: number | undefined = undefined;
|
||||||
let open = false;
|
let open = false;
|
||||||
let minWidth = 0;
|
|
||||||
|
|
||||||
$: watchSelectedIndex(selectedIndex);
|
$: watchSelectedIndex(selectedIndex);
|
||||||
$: watchActiveEntry(activeEntry);
|
$: watchActiveEntry(activeEntry);
|
||||||
|
@ -76,11 +77,15 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<LayoutRow class="dropdown-input" bind:this={self} data-dropdown-input>
|
<LayoutRow
|
||||||
|
class="dropdown-input"
|
||||||
|
styles={{ ...(minWidth > 0 ? { "min-width": `${minWidth}px` } : {}), ...(maxWidth > 0 ? { "max-width": `${maxWidth}px` } : {}) }}
|
||||||
|
bind:this={self}
|
||||||
|
data-dropdown-input
|
||||||
|
>
|
||||||
<LayoutRow
|
<LayoutRow
|
||||||
class="dropdown-box"
|
class="dropdown-box"
|
||||||
classes={{ disabled, open }}
|
classes={{ disabled, open }}
|
||||||
styles={{ "min-width": `${minWidth}px` }}
|
|
||||||
{tooltip}
|
{tooltip}
|
||||||
on:click={() => !disabled && (open = true)}
|
on:click={() => !disabled && (open = true)}
|
||||||
on:blur={unFocusDropdownBox}
|
on:blur={unFocusDropdownBox}
|
||||||
|
|
|
@ -104,7 +104,15 @@
|
||||||
|
|
||||||
<!-- TODO: Combine this widget into the DropdownInput widget -->
|
<!-- TODO: Combine this widget into the DropdownInput widget -->
|
||||||
<LayoutRow class="font-input">
|
<LayoutRow class="font-input">
|
||||||
<LayoutRow class="dropdown-box" classes={{ disabled }} styles={{ "min-width": `${minWidth}px` }} {tooltip} tabindex={disabled ? -1 : 0} on:click={toggleOpen} data-floating-menu-spawner>
|
<LayoutRow
|
||||||
|
class="dropdown-box"
|
||||||
|
classes={{ disabled }}
|
||||||
|
styles={{ ...(minWidth > 0 ? { "min-width": `${minWidth}px` } : {}) }}
|
||||||
|
{tooltip}
|
||||||
|
tabindex={disabled ? -1 : 0}
|
||||||
|
on:click={toggleOpen}
|
||||||
|
data-floating-menu-spawner
|
||||||
|
>
|
||||||
<TextLabel class="dropdown-label">{activeEntry?.value || ""}</TextLabel>
|
<TextLabel class="dropdown-label">{activeEntry?.value || ""}</TextLabel>
|
||||||
<IconLabel class="dropdown-arrow" icon="DropdownArrow" />
|
<IconLabel class="dropdown-arrow" icon="DropdownArrow" />
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
|
|
|
@ -53,6 +53,7 @@
|
||||||
|
|
||||||
// Styling
|
// Styling
|
||||||
export let minWidth = 0;
|
export let minWidth = 0;
|
||||||
|
export let maxWidth = 0;
|
||||||
|
|
||||||
// Callbacks
|
// Callbacks
|
||||||
export let incrementCallbackIncrease: (() => void) | undefined = undefined;
|
export let incrementCallbackIncrease: (() => void) | undefined = undefined;
|
||||||
|
@ -88,6 +89,7 @@
|
||||||
$: sliderStepValue = isInteger ? (step === undefined ? 1 : step) : "any";
|
$: sliderStepValue = isInteger ? (step === undefined ? 1 : step) : "any";
|
||||||
$: styles = {
|
$: styles = {
|
||||||
...(minWidth > 0 ? { "min-width": `${minWidth}px` } : {}),
|
...(minWidth > 0 ? { "min-width": `${minWidth}px` } : {}),
|
||||||
|
...(maxWidth > 0 ? { "max-width": `${maxWidth}px` } : {}),
|
||||||
...(mode === "Range" ? { "--progress-factor": Math.min(Math.max((rangeSliderValueAsRendered - rangeMin) / (rangeMax - rangeMin), 0), 1) } : {}),
|
...(mode === "Range" ? { "--progress-factor": Math.min(Math.max((rangeSliderValueAsRendered - rangeMin) / (rangeMax - rangeMin), 0), 1) } : {}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<LayoutRow class="radio-input" classes={{ disabled }} styles={{ "min-width": minWidth > 0 ? `${minWidth}px` : "" }}>
|
<LayoutRow class="radio-input" classes={{ disabled }} styles={{ ...(minWidth > 0 ? { "min-width": `${minWidth}px` } : {}) }}>
|
||||||
{#each entries as entry, index}
|
{#each entries as entry, index}
|
||||||
<button class:active={index === selectedIndex} class:mixed class:disabled on:click={() => handleEntryClick(entry)} title={entry.tooltip} tabindex={index === selectedIndex ? -1 : 0} {disabled}>
|
<button class:active={index === selectedIndex} class:mixed class:disabled on:click={() => handleEntryClick(entry)} title={entry.tooltip} tabindex={index === selectedIndex ? -1 : 0} {disabled}>
|
||||||
{#if entry.icon}
|
{#if entry.icon}
|
||||||
|
|
|
@ -63,7 +63,7 @@
|
||||||
<FieldInput
|
<FieldInput
|
||||||
class={`text-input ${className}`.trim()}
|
class={`text-input ${className}`.trim()}
|
||||||
classes={{ centered, ...classes }}
|
classes={{ centered, ...classes }}
|
||||||
styles={{ "min-width": minWidth > 0 ? `${minWidth}px` : undefined }}
|
styles={{ ...(minWidth > 0 ? { "min-width": `${minWidth}px` } : {}) }}
|
||||||
{value}
|
{value}
|
||||||
on:value
|
on:value
|
||||||
on:textFocused={onTextFocused}
|
on:textFocused={onTextFocused}
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
class:multiline
|
class:multiline
|
||||||
class:center-align={centerAlign}
|
class:center-align={centerAlign}
|
||||||
class:table-align={tableAlign}
|
class:table-align={tableAlign}
|
||||||
style:min-width={minWidth > 0 ? `${minWidth}px` : ""}
|
style:min-width={minWidth > 0 ? `${minWidth}px` : undefined}
|
||||||
style={`${styleName} ${extraStyles}`.trim() || undefined}
|
style={`${styleName} ${extraStyles}`.trim() || undefined}
|
||||||
title={tooltip}
|
title={tooltip}
|
||||||
>
|
>
|
||||||
|
|
|
@ -1085,6 +1085,12 @@ export class DropdownInput extends WidgetProps {
|
||||||
|
|
||||||
@Transform(({ value }: { value: string }) => value || undefined)
|
@Transform(({ value }: { value: string }) => value || undefined)
|
||||||
tooltip!: string | undefined;
|
tooltip!: string | undefined;
|
||||||
|
|
||||||
|
// Styling
|
||||||
|
|
||||||
|
minWidth!: number;
|
||||||
|
|
||||||
|
maxWidth!: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FontInput extends WidgetProps {
|
export class FontInput extends WidgetProps {
|
||||||
|
@ -1185,6 +1191,8 @@ export class NumberInput extends WidgetProps {
|
||||||
// Styling
|
// Styling
|
||||||
|
|
||||||
minWidth!: number;
|
minWidth!: number;
|
||||||
|
|
||||||
|
maxWidth!: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NodeCatalog extends WidgetProps {
|
export class NodeCatalog extends WidgetProps {
|
||||||
|
@ -1194,6 +1202,8 @@ export class NodeCatalog extends WidgetProps {
|
||||||
export class PopoverButton extends WidgetProps {
|
export class PopoverButton extends WidgetProps {
|
||||||
style!: PopoverButtonStyle | undefined;
|
style!: PopoverButtonStyle | undefined;
|
||||||
|
|
||||||
|
menuDirection!: MenuDirection | undefined;
|
||||||
|
|
||||||
icon!: IconName | undefined;
|
icon!: IconName | undefined;
|
||||||
|
|
||||||
disabled!: boolean;
|
disabled!: boolean;
|
||||||
|
@ -1207,6 +1217,8 @@ export class PopoverButton extends WidgetProps {
|
||||||
popoverMinWidth: number | undefined;
|
popoverMinWidth: number | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type MenuDirection = "Top" | "Bottom" | "Left" | "Right" | "TopLeft" | "TopRight" | "BottomLeft" | "BottomRight" | "Center";
|
||||||
|
|
||||||
export type RadioEntryData = {
|
export type RadioEntryData = {
|
||||||
value?: string;
|
value?: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
|
@ -1581,7 +1593,11 @@ export class UpdateDocumentBarLayout extends WidgetDiffUpdate {}
|
||||||
|
|
||||||
export class UpdateDocumentModeLayout extends WidgetDiffUpdate {}
|
export class UpdateDocumentModeLayout extends WidgetDiffUpdate {}
|
||||||
|
|
||||||
export class UpdateLayersPanelControlBarLayout extends WidgetDiffUpdate {}
|
export class UpdateLayersPanelControlBarLeftLayout extends WidgetDiffUpdate {}
|
||||||
|
|
||||||
|
export class UpdateLayersPanelControlBarRightLayout extends WidgetDiffUpdate {}
|
||||||
|
|
||||||
|
export class UpdateLayersPanelBottomBarLayout extends WidgetDiffUpdate {}
|
||||||
|
|
||||||
// Extends JsMessage instead of WidgetDiffUpdate because the menu bar isn't diffed
|
// Extends JsMessage instead of WidgetDiffUpdate because the menu bar isn't diffed
|
||||||
export class UpdateMenuBarLayout extends JsMessage {
|
export class UpdateMenuBarLayout extends JsMessage {
|
||||||
|
@ -1680,7 +1696,9 @@ export const messageMakers: Record<string, MessageMaker> = {
|
||||||
UpdateImportsExports,
|
UpdateImportsExports,
|
||||||
UpdateInputHints,
|
UpdateInputHints,
|
||||||
UpdateInSelectedNetwork,
|
UpdateInSelectedNetwork,
|
||||||
UpdateLayersPanelControlBarLayout,
|
UpdateLayersPanelControlBarLeftLayout,
|
||||||
|
UpdateLayersPanelControlBarRightLayout,
|
||||||
|
UpdateLayersPanelBottomBarLayout,
|
||||||
UpdateLayerWidths,
|
UpdateLayerWidths,
|
||||||
UpdateMenuBarLayout,
|
UpdateMenuBarLayout,
|
||||||
UpdateMouseCursor,
|
UpdateMouseCursor,
|
||||||
|
|
|
@ -10,6 +10,7 @@ const GRAPHICS = {
|
||||||
// 12px Solid
|
// 12px Solid
|
||||||
import Add from "@graphite-frontend/assets/icon-12px-solid/add.svg";
|
import Add from "@graphite-frontend/assets/icon-12px-solid/add.svg";
|
||||||
import Checkmark from "@graphite-frontend/assets/icon-12px-solid/checkmark.svg";
|
import Checkmark from "@graphite-frontend/assets/icon-12px-solid/checkmark.svg";
|
||||||
|
import Clipped from "@graphite-frontend/assets/icon-12px-solid/clipped.svg";
|
||||||
import CloseX from "@graphite-frontend/assets/icon-12px-solid/close-x.svg";
|
import CloseX from "@graphite-frontend/assets/icon-12px-solid/close-x.svg";
|
||||||
import Delay from "@graphite-frontend/assets/icon-12px-solid/delay.svg";
|
import Delay from "@graphite-frontend/assets/icon-12px-solid/delay.svg";
|
||||||
import DropdownArrow from "@graphite-frontend/assets/icon-12px-solid/dropdown-arrow.svg";
|
import DropdownArrow from "@graphite-frontend/assets/icon-12px-solid/dropdown-arrow.svg";
|
||||||
|
@ -51,6 +52,7 @@ import WorkingColors from "@graphite-frontend/assets/icon-12px-solid/working-col
|
||||||
const SOLID_12PX = {
|
const SOLID_12PX = {
|
||||||
Add: { svg: Add, size: 12 },
|
Add: { svg: Add, size: 12 },
|
||||||
Checkmark: { svg: Checkmark, size: 12 },
|
Checkmark: { svg: Checkmark, size: 12 },
|
||||||
|
Clipped: { svg: Clipped, size: 12 },
|
||||||
CloseX: { svg: CloseX, size: 12 },
|
CloseX: { svg: CloseX, size: 12 },
|
||||||
Delay: { svg: Delay, size: 12 },
|
Delay: { svg: Delay, size: 12 },
|
||||||
DropdownArrow: { svg: DropdownArrow, size: 12 },
|
DropdownArrow: { svg: DropdownArrow, size: 12 },
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue