Many subtle improvements to the UI design system (#1537)

This commit is contained in:
Keavon Chambers 2023-12-28 04:35:20 -08:00 committed by GitHub
parent 34f952bad1
commit 96b5d7b520
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 326 additions and 521 deletions

View File

@ -29,7 +29,7 @@ impl DialogLayoutHolder for AboutGraphiteDialog {
.map(|(icon, label, url)| {
TextButton::new(label)
.icon(Some(icon.into()))
.no_background(true)
.flush(true)
.on_update(|_| FrontendMessage::TriggerVisitLink { url: url.into() }.into())
.widget_holder()
})
@ -40,7 +40,7 @@ impl DialogLayoutHolder for AboutGraphiteDialog {
widgets.push(
TextButton::new("Licenses")
.icon(Some("License".into()))
.no_background(true)
.flush(true)
.on_update(move |_| {
DialogMessage::RequestLicensesDialogWithLocalizedCommitDate {
localized_commit_year: localized_commit_year.clone(),

View File

@ -28,7 +28,7 @@ impl LayoutHolder for ComingSoonDialog {
let row2 = vec![TextLabel::new("But you can help build it! Visit its issue:").widget_holder()];
let row3 = vec![TextButton::new(format!("GitHub Issue #{issue}"))
.icon(Some("Website".into()))
.no_background(true)
.flush(true)
.on_update(move |_| {
FrontendMessage::TriggerVisitLink {
url: format!("https://github.com/GraphiteEditor/Graphite/issues/{issue}"),

View File

@ -28,7 +28,7 @@ impl DialogLayoutHolder for LicensesDialog {
.map(|(icon, label, url)| {
TextButton::new(label)
.icon(Some(icon.into()))
.no_background(true)
.flush(true)
.on_update(|_| FrontendMessage::TriggerVisitLink { url: url.into() }.into())
.widget_holder()
})

View File

@ -173,11 +173,6 @@ pub enum FrontendMessage {
#[serde(rename = "setColorChoice")]
set_color_choice: Option<String>,
},
UpdateGraphViewOverlayButtonLayout {
#[serde(rename = "layoutTarget")]
layout_target: LayoutTarget,
diff: Vec<WidgetDiff>,
},
UpdateImageData {
#[serde(rename = "documentId")]
document_id: DocumentId,

View File

@ -264,6 +264,8 @@ pub fn default_mapping() -> Mapping {
entry!(KeyDown(KeyC); modifiers=[Alt], action_dispatch=ToolMessage::SelectRandomPrimaryColor),
//
// DocumentMessage
entry!(KeyDown(Space); modifiers=[Control], action_dispatch=DocumentMessage::GraphViewOverlayToggle),
entry!(KeyUp(Escape); action_dispatch=DocumentMessage::GraphViewOverlay { open: false }),
entry!(KeyDown(Delete); action_dispatch=DocumentMessage::DeleteSelectedLayers),
entry!(KeyDown(Backspace); action_dispatch=DocumentMessage::DeleteSelectedLayers),
entry!(KeyDown(KeyP); modifiers=[Alt], action_dispatch=DocumentMessage::DebugPrintDocument),
@ -337,8 +339,6 @@ pub fn default_mapping() -> Mapping {
entry!(KeyDown(Period); action_dispatch=NavigationMessage::FitViewportToSelection),
//
// PortfolioMessage
entry!(KeyDown(Space); modifiers=[Control], action_dispatch=PortfolioMessage::GraphViewOverlayToggle),
entry!(KeyUp(Escape); action_dispatch=PortfolioMessage::GraphViewOverlay { open: false }),
entry!(KeyDown(Tab); modifiers=[Control], action_dispatch=PortfolioMessage::NextDocument),
entry!(KeyDown(Tab); modifiers=[Control, Shift], action_dispatch=PortfolioMessage::PrevDocument),
entry!(KeyDown(KeyW); modifiers=[Accel], action_dispatch=PortfolioMessage::CloseActiveDocumentWithConfirmation),

View File

@ -171,12 +171,6 @@ impl<F: Fn(&MessageDiscriminant) -> Vec<KeysGroup>> MessageHandler<LayoutMessage
},
_ => {} // If it's some other type we could just ignore it and leave the value as is
},
Widget::OptionalInput(optional_input) => {
let update_value = value.as_bool().expect("OptionalInput update was not of type: bool");
optional_input.checked = update_value;
let callback_message = (optional_input.on_update.callback)(optional_input);
responses.add(callback_message);
}
Widget::ParameterExposeButton(parameter_expose_button) => {
let callback_message = (parameter_expose_button.on_update.callback)(parameter_expose_button);
responses.add(callback_message);
@ -212,7 +206,7 @@ impl<F: Fn(&MessageDiscriminant) -> Vec<KeysGroup>> MessageHandler<LayoutMessage
responses.add(callback_message);
}
Widget::TextLabel(_) => {}
Widget::WorkingColorsButton(_) => {}
Widget::WorkingColorsInput(_) => {}
};
responses.add(ResendActiveWidget { layout_target, dirty_id: widget_id });
}
@ -273,7 +267,6 @@ impl LayoutMessageHandler {
LayoutTarget::DialogColumn2 => FrontendMessage::UpdateDialogColumn2 { layout_target, diff },
LayoutTarget::DocumentBar => FrontendMessage::UpdateDocumentBarLayout { layout_target, diff },
LayoutTarget::DocumentMode => FrontendMessage::UpdateDocumentModeLayout { layout_target, diff },
LayoutTarget::GraphViewOverlayButton => FrontendMessage::UpdateGraphViewOverlayButtonLayout { layout_target, diff },
LayoutTarget::LayersPanelOptions => FrontendMessage::UpdateLayersPanelOptionsLayout { layout_target, diff },
LayoutTarget::MenuBar => unreachable!("Menu bar is not diffed"),
LayoutTarget::NodeGraphBar => FrontendMessage::UpdateNodeGraphBarLayout { layout_target, diff },

View File

@ -34,8 +34,6 @@ pub enum LayoutTarget {
DocumentBar,
/// Contains the dropdown for design / select / guide mode found on the top left of the canvas.
DocumentMode,
/// The button below the tool shelf and directly above the working colors which lets the user toggle the node graph overlaid on the canvas.
GraphViewOverlayButton,
/// Options for opacity seen at the top of the Layers panel.
LayersPanelOptions,
/// The dropdown menu at the very top of the application: File, Edit, etc.
@ -337,7 +335,6 @@ impl LayoutGroup {
Widget::IconLabel(x) => &mut x.tooltip,
Widget::ImageLabel(x) => &mut x.tooltip,
Widget::NumberInput(x) => &mut x.tooltip,
Widget::OptionalInput(x) => &mut x.tooltip,
Widget::ParameterExposeButton(x) => &mut x.tooltip,
Widget::PopoverButton(x) => &mut x.tooltip,
Widget::TextAreaInput(x) => &mut x.tooltip,
@ -345,7 +342,7 @@ impl LayoutGroup {
Widget::TextInput(x) => &mut x.tooltip,
Widget::TextLabel(x) => &mut x.tooltip,
Widget::BreadcrumbTrailButtons(x) => &mut x.tooltip,
Widget::InvisibleStandinInput(_) | Widget::PivotInput(_) | Widget::RadioInput(_) | Widget::Separator(_) | Widget::WorkingColorsButton(_) => continue,
Widget::InvisibleStandinInput(_) | Widget::PivotInput(_) | Widget::RadioInput(_) | Widget::Separator(_) | Widget::WorkingColorsInput(_) => continue,
};
if val.is_empty() {
*val = tooltip.clone();
@ -491,7 +488,6 @@ pub enum Widget {
ImageLabel(ImageLabel),
InvisibleStandinInput(InvisibleStandinInput),
NumberInput(NumberInput),
OptionalInput(OptionalInput),
ParameterExposeButton(ParameterExposeButton),
PivotInput(PivotInput),
PopoverButton(PopoverButton),
@ -501,7 +497,7 @@ pub enum Widget {
TextButton(TextButton),
TextInput(TextInput),
TextLabel(TextLabel),
WorkingColorsButton(WorkingColorsButton),
WorkingColorsInput(WorkingColorsInput),
}
/// A single change to part of the UI, containing the location of the change and the new value.
@ -560,7 +556,6 @@ impl DiffUpdate {
Widget::FontInput(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
Widget::IconButton(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
Widget::NumberInput(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
Widget::OptionalInput(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
Widget::ParameterExposeButton(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
Widget::PopoverButton(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
Widget::TextButton(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
@ -574,7 +569,7 @@ impl DiffUpdate {
| Widget::TextAreaInput(_)
| Widget::TextInput(_)
| Widget::TextLabel(_)
| Widget::WorkingColorsButton(_) => None,
| Widget::WorkingColorsInput(_) => None,
};
if let Some((tooltip, Some(tooltip_shortcut))) = &mut tooltip_shortcut {
apply_shortcut_to_tooltip(tooltip_shortcut, tooltip);

View File

@ -35,6 +35,8 @@ pub struct IconButton {
#[derive(Clone, Serialize, Deserialize, Derivative, WidgetBuilder, specta::Type)]
#[derivative(Debug, PartialEq, Default)]
pub struct PopoverButton {
pub style: Option<String>,
pub icon: Option<String>,
pub disabled: bool,
@ -85,8 +87,7 @@ pub struct TextButton {
pub icon: Option<String>,
#[serde(rename = "noBackground")]
pub no_background: bool,
pub flush: bool,
pub emphasized: bool,
@ -109,16 +110,6 @@ 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

@ -1,7 +1,7 @@
use crate::messages::input_mapper::utility_types::misc::ActionKeys;
use crate::messages::layout::utility_types::widget_prelude::*;
use graphene_core::raster::curve::Curve;
use graphene_core::{raster::curve::Curve, Color};
use graphite_proc_macros::WidgetBuilder;
use derivative::*;
@ -253,28 +253,6 @@ pub enum NumberInputMode {
Range,
}
#[derive(Clone, Default, Derivative, Serialize, Deserialize, WidgetBuilder, specta::Type)]
#[derivative(Debug, PartialEq)]
pub struct OptionalInput {
#[widget_builder(constructor)]
pub checked: bool,
pub disabled: bool,
#[widget_builder(constructor)]
pub icon: String,
pub tooltip: String,
#[serde(skip)]
pub tooltip_shortcut: Option<ActionKeys>,
// Callbacks
#[serde(skip)]
#[derivative(Debug = "ignore", PartialEq = "ignore")]
pub on_update: WidgetCallback<OptionalInput>,
}
#[derive(Clone, Default, Derivative, Serialize, Deserialize, WidgetBuilder, specta::Type)]
#[derivative(Debug, PartialEq)]
pub struct RadioInput {
@ -310,6 +288,16 @@ pub struct RadioEntryData {
pub on_update: WidgetCallback<()>,
}
#[derive(Clone, Serialize, Deserialize, Derivative, WidgetBuilder, specta::Type)]
#[derivative(Debug, PartialEq, Default)]
pub struct WorkingColorsInput {
#[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 {

View File

@ -46,7 +46,6 @@ pub enum SeparatorType {
#[default]
Unrelated,
Section,
List,
}
#[derive(Clone, Serialize, Deserialize, Derivative, Debug, PartialEq, Eq, Default, WidgetBuilder, specta::Type)]

View File

@ -59,6 +59,10 @@ pub enum DocumentMessage {
FlipSelectedLayers {
flip_axis: FlipAxis,
},
GraphViewOverlay {
open: bool,
},
GraphViewOverlayToggle,
GroupSelectedLayers,
ImaginateGenerate,
ImaginateRandom {

View File

@ -81,6 +81,8 @@ pub struct DocumentMessageHandler {
#[serde(skip)]
undo_in_progress: bool,
#[serde(skip)]
graph_view_overlay_open: bool,
#[serde(skip)]
pub snapping_state: SnappingState,
#[serde(skip)]
layer_range_selection_reference: Option<LayerNodeIdentifier>,
@ -119,6 +121,7 @@ impl Default for DocumentMessageHandler {
saved_hash: None,
auto_saved_hash: None,
undo_in_progress: false,
graph_view_overlay_open: false,
snapping_state: SnappingState::default(),
layer_range_selection_reference: None,
metadata: Default::default(),
@ -227,7 +230,6 @@ pub struct DocumentInputs<'a> {
pub ipp: &'a InputPreprocessorMessageHandler,
pub persistent_data: &'a PersistentData,
pub executor: &'a mut NodeGraphExecutor,
pub graph_view_overlay_open: bool,
}
impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHandler {
@ -238,7 +240,6 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
ipp,
persistent_data,
executor,
graph_view_overlay_open,
} = document_inputs;
use DocumentMessage::*;
@ -282,7 +283,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
document_name: self.name.as_str(),
collapsed: &mut self.collapsed,
input: ipp,
graph_view_overlay_open,
graph_view_overlay_open: self.graph_view_overlay_open,
},
);
}
@ -382,7 +383,11 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
self.update_layers_panel_options_bar_widgets(responses);
let data_buffer: RawBuffer = self.serialize_root();
responses.add(FrontendMessage::UpdateDocumentLayerStructure { data_buffer })
responses.add(FrontendMessage::UpdateDocumentLayerStructure { data_buffer });
if self.graph_view_overlay_open {
responses.add(NodeGraphMessage::SendGraph { should_rerender: false });
}
}
DuplicateSelectedLayers => {
// TODO: Reimplement selected layer duplication
@ -412,6 +417,17 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
responses.add(BroadcastEvent::DocumentIsDirty);
}
}
GraphViewOverlay { open } => {
self.graph_view_overlay_open = open;
if open {
responses.add(NodeGraphMessage::SendGraph { should_rerender: false });
}
responses.add(FrontendMessage::TriggerGraphViewOverlay { open });
}
GraphViewOverlayToggle => {
responses.add(DocumentMessage::GraphViewOverlay { open: !self.graph_view_overlay_open });
}
GroupSelectedLayers => {
// TODO: Add code that changes the insert index of the new folder based on the selected layer
let parent = self.metadata().deepest_common_ancestor(self.metadata().selected_layers(), true).unwrap_or(LayerNodeIdentifier::ROOT);
@ -1089,9 +1105,10 @@ impl DocumentMessageHandler {
pub fn update_document_widgets(&self, responses: &mut VecDeque<Message>) {
let snapping_state = self.snapping_state.clone();
let mut widgets = vec![
OptionalInput::new(snapping_state.snapping_enabled, "Snapping")
CheckboxInput::new(snapping_state.snapping_enabled)
.icon("Snapping")
.tooltip("Snapping")
.on_update(move |optional_input: &OptionalInput| {
.on_update(move |optional_input: &CheckboxInput| {
let snapping_enabled = optional_input.checked;
DocumentMessage::SetSnapping {
snapping_enabled: Some(snapping_enabled),
@ -1105,6 +1122,8 @@ impl DocumentMessageHandler {
.options_widget(vec![
LayoutGroup::Row {
widgets: vec![
TextLabel::new(SnappingOptions::BoundingBoxes.to_string()).table_align(true).min_width(96).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
CheckboxInput::new(snapping_state.bounding_box_snapping)
.tooltip(SnappingOptions::BoundingBoxes.to_string())
.on_update(move |input: &CheckboxInput| {
@ -1116,13 +1135,13 @@ impl DocumentMessageHandler {
.into()
})
.widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
TextLabel::new(SnappingOptions::BoundingBoxes.to_string()).table_align(false).min_width(60).widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(),
],
},
LayoutGroup::Row {
widgets: vec![
TextLabel::new(SnappingOptions::Points.to_string()).table_align(true).min_width(96).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
CheckboxInput::new(self.snapping_state.node_snapping)
.tooltip(SnappingOptions::Points.to_string())
.on_update(|input: &CheckboxInput| {
@ -1134,22 +1153,22 @@ impl DocumentMessageHandler {
.into()
})
.widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
TextLabel::new(SnappingOptions::Points.to_string()).table_align(false).min_width(60).widget_holder(),
],
},
])
.widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
OptionalInput::new(true, "Grid")
Separator::new(SeparatorType::Related).widget_holder(),
CheckboxInput::new(true)
.icon("Grid")
.tooltip("Grid")
.on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(318) }.into())
.widget_holder(),
PopoverButton::new("Grid", "Coming soon").widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
OptionalInput::new(self.overlays_visible, "Overlays")
Separator::new(SeparatorType::Related).widget_holder(),
CheckboxInput::new(self.overlays_visible)
.icon("Overlays")
.tooltip("Overlays")
.on_update(|optional_input: &OptionalInput| DocumentMessage::SetOverlaysVisibility { visible: optional_input.checked }.into())
.on_update(|optional_input: &CheckboxInput| DocumentMessage::SetOverlaysVisibility { visible: optional_input.checked }.into())
.widget_holder(),
PopoverButton::new("Overlays", "Coming soon").widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
@ -1176,7 +1195,7 @@ impl DocumentMessageHandler {
})
.widget_holder(),
PopoverButton::new("View Mode", "Coming soon").widget_holder(),
Separator::new(SeparatorType::Section).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
IconButton::new("ZoomIn", 24)
.tooltip("Zoom In")
.tooltip_shortcut(action_keys!(NavigationMessageDiscriminant::IncreaseCanvasZoom))
@ -1192,6 +1211,11 @@ impl DocumentMessageHandler {
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::ZoomCanvasTo100Percent))
.on_update(|_| NavigationMessage::SetCanvasZoom { zoom_factor: 1. }.into())
.widget_holder(),
PopoverButton::new(
"Canvas Navigation",
"Interactive controls in this\nmenu are coming soon.\n\nPan:\n• Middle Click Drag\n\nTilt:\n• Alt + Middle Click Drag\n\nZoom:\n• Shift + Middle Click Drag\n• Ctrl + Scroll Wheel Roll",
)
.widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(),
NumberInput::new(Some(self.navigation_handler.snapped_scale(self.navigation.zoom) * 100.))
.unit("%")
@ -1208,6 +1232,13 @@ impl DocumentMessageHandler {
.increment_callback_decrease(|_| NavigationMessage::DecreaseCanvasZoom { center_on_mouse: false }.into())
.increment_callback_increase(|_| NavigationMessage::IncreaseCanvasZoom { center_on_mouse: false }.into())
.widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
TextButton::new("Node Graph")
.icon(Some(if self.graph_view_overlay_open { "GraphViewOpen".into() } else { "GraphViewClosed".into() }))
.tooltip(if self.graph_view_overlay_open { "Hide Node Graph" } else { "Show Node Graph" })
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::GraphViewOverlayToggle))
.on_update(move |_| DocumentMessage::GraphViewOverlayToggle.into())
.widget_holder(),
];
let rotation_value = self.navigation_handler.snapped_angle(self.navigation.tilt) / (std::f64::consts::PI / 180.);
if rotation_value.abs() > 0.00001 {
@ -1225,14 +1256,6 @@ impl DocumentMessageHandler {
.widget_holder(),
]);
}
widgets.extend([
Separator::new(SeparatorType::Related).widget_holder(),
PopoverButton::new(
"Canvas Navigation",
"Interactive options in this popover\nmenu are coming soon.\n\nZoom:\n• Shift + Middle Click Drag\n• Ctrl + Scroll Wheel Roll\nRotate:\n• Alt + Left Click Drag",
)
.widget_holder(),
]);
let document_bar_layout = WidgetLayout::new(vec![LayoutGroup::Row { widgets }]);
let document_mode_layout = WidgetLayout::new(vec![LayoutGroup::Row {
@ -1339,7 +1362,7 @@ impl DocumentMessageHandler {
}
})
.widget_holder(),
Separator::new(SeparatorType::Section).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
IconButton::new("Folder", 24)
.tooltip("New Folder")
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::CreateEmptyFolder))
@ -1398,7 +1421,7 @@ impl DocumentMessageHandler {
responses.add(DocumentMessage::MoveSelectedLayersTo { parent, insert_index });
}
pub fn actions_with_graph_open(&self, graph_open: bool) -> ActionList {
pub fn actions_with_graph_open(&self) -> ActionList {
let mut common = actions!(DocumentMessageDiscriminant;
Undo,
Redo,
@ -1411,9 +1434,15 @@ impl DocumentMessageHandler {
ZoomCanvasToFitAll,
ZoomCanvasTo100Percent,
ZoomCanvasTo200Percent,
GraphViewOverlayToggle,
CreateEmptyFolder,
);
if self.graph_view_overlay_open {
let escape = actions!(DocumentMessageDiscriminant; GraphViewOverlay);
common.extend(escape);
}
if self.metadata().selected_layers().next().is_some() {
let select = actions!(DocumentMessageDiscriminant;
DeleteSelectedLayers,
@ -1429,7 +1458,7 @@ impl DocumentMessageHandler {
common.extend(select);
}
common.extend(self.navigation_handler.actions());
common.extend(self.node_graph_handler.actions_with_node_graph_open(graph_open));
common.extend(self.node_graph_handler.actions_with_node_graph_open(self.graph_view_overlay_open));
common
}
}

View File

@ -130,14 +130,22 @@ pub struct NodeGraphMessageHandler {
impl Default for NodeGraphMessageHandler {
fn default() -> Self {
// TODO: Replace this with an "Add Node" button, also next to an "Add Layer" button
let add_nodes_label = TextLabel::new("Right Click Graph to Add Nodes").italic(true).widget_holder();
let add_nodes_label_row = LayoutGroup::Row { widgets: vec![add_nodes_label] };
let right_side_widgets = vec![
// TODO: Replace this with an "Add Node" button, also next to an "Add Layer" button
TextLabel::new("Right Click in Graph to Add Nodes").italic(true).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
TextButton::new("Node Graph")
.icon(Some("GraphViewOpen".into()))
.tooltip("Hide Node Graph")
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::GraphViewOverlayToggle))
.on_update(move |_| DocumentMessage::GraphViewOverlayToggle.into())
.widget_holder(),
];
Self {
network: Vec::new(),
has_selection: false,
widgets: [add_nodes_label_row, LayoutGroup::default()],
widgets: [LayoutGroup::Row { widgets: Vec::new() }, LayoutGroup::Row { widgets: right_side_widgets }],
}
}
}
@ -161,8 +169,6 @@ impl NodeGraphMessageHandler {
// If there is at least one other selected node then show the hide or show button
if selected_nodes.next().is_some() {
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
// Check if any of the selected nodes are disabled
let is_hidden = document_metadata.selected_nodes().any(|id| network.disabled.contains(id));
@ -178,13 +184,13 @@ impl NodeGraphMessageHandler {
.on_update(move |_| NodeGraphMessage::ToggleSelectedHidden.into())
.widget_holder();
widgets.push(hide_button);
widgets.push(Separator::new(SeparatorType::Related).widget_holder());
}
// If only one node is selected then show the preview or stop previewing button
let mut selected_nodes = document_metadata.selected_nodes();
if let (Some(&node_id), None) = (selected_nodes.next(), selected_nodes.next()) {
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
// Is this node the current output
let is_output = network.outputs_contain(node_id);
@ -199,7 +205,7 @@ impl NodeGraphMessageHandler {
}
}
self.widgets[1] = LayoutGroup::Row { widgets };
self.widgets[0] = LayoutGroup::Row { widgets };
}
self.send_node_bar_layout(responses);
}

View File

@ -52,22 +52,19 @@ fn expose_widget(node_id: NodeId, index: usize, data_type: FrontendGraphDataType
.widget_holder()
}
// TODO: Remove this when we have proper entry row formatting that includes room for Assists.
fn add_blank_assist(widgets: &mut Vec<WidgetHolder>) {
widgets.extend_from_slice(&[
Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: These three separators add up to 24px,
Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: which is the width of the Assist area.
Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: Remove these when we have proper entry row formatting that includes room for Assists.
Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: This last one is the separator after the 24px assist.
// Custom CSS specific to the Properties panel converts this Section separator into the width of an assist (24px).
Separator::new(SeparatorType::Section).widget_holder(),
// This last one is the separator after the 24px assist.
Separator::new(SeparatorType::Unrelated).widget_holder(),
]);
}
fn start_widgets(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, data_type: FrontendGraphDataType, blank_assist: bool) -> Vec<WidgetHolder> {
let input = document_node.inputs.get(index).expect("A widget failed to be built because its node's input index is invalid.");
let mut widgets = vec![
expose_widget(node_id, index, data_type, input.is_exposed()),
Separator::new(SeparatorType::Unrelated).widget_holder(),
TextLabel::new(name).widget_holder(),
];
let mut widgets = vec![expose_widget(node_id, index, data_type, input.is_exposed()), TextLabel::new(name).widget_holder()];
if blank_assist {
add_blank_assist(&mut widgets);
}
@ -291,18 +288,17 @@ fn font_inputs(document_node: &DocumentNode, node_id: NodeId, index: usize, name
.on_update(update_value(from_font_input, node_id, index))
.widget_holder(),
]);
second_widgets = Some(vec![
TextLabel::new("").widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
let mut second_row = vec![TextLabel::new("").widget_holder()];
add_blank_assist(&mut second_row);
second_row.extend_from_slice(&[
Separator::new(SeparatorType::Unrelated).widget_holder(),
FontInput::new(font.font_family.clone(), font.font_style.clone())
.is_style_picker(true)
.on_update(update_value(from_font_input, node_id, index))
.widget_holder(),
]);
second_widgets = Some(second_row);
}
(first_widgets, second_widgets)
}
@ -1559,16 +1555,10 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
let transform_not_connected = false;
let progress = {
let mut widgets = vec![TextLabel::new("Progress").widget_holder(), Separator::new(SeparatorType::Unrelated).widget_holder()];
add_blank_assist(&mut widgets);
let status = imaginate_status.to_text();
let widgets = vec![
TextLabel::new("Progress").widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: These three separators add up to 24px,
Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: which is the width of the Assist area.
Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: Remove these when we have proper entry row formatting that includes room for Assists.
Separator::new(SeparatorType::Unrelated).widget_holder(),
TextLabel::new(status.as_ref()).bold(true).widget_holder(),
];
widgets.push(TextLabel::new(status.as_ref()).bold(true).widget_holder());
LayoutGroup::Row { widgets }.with_tooltip(match imaginate_status {
ImaginateStatus::Failed(_) => status.as_ref(),
_ => "When generating, the percentage represents how many sampling steps have so far been processed out of the target number",
@ -1577,20 +1567,14 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
let image_controls = {
let mut widgets = vec![TextLabel::new("Image").widget_holder(), Separator::new(SeparatorType::Unrelated).widget_holder()];
let assist_separators = [
Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: These three separators add up to 24px,
Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: which is the width of the Assist area.
Separator::new(SeparatorType::Unrelated).widget_holder(), // TODO: Remove these when we have proper entry row formatting that includes room for Assists.
Separator::new(SeparatorType::Unrelated).widget_holder(),
];
match &imaginate_status {
ImaginateStatus::Beginning | ImaginateStatus::Uploading => {
widgets.extend_from_slice(&assist_separators);
add_blank_assist(&mut widgets);
widgets.push(TextButton::new("Beginning...").tooltip("Sending image generation request to the server").disabled(true).widget_holder());
}
ImaginateStatus::Generating(_) => {
widgets.extend_from_slice(&assist_separators);
add_blank_assist(&mut widgets);
widgets.push(
TextButton::new("Terminate")
.tooltip("Cancel the in-progress image generation and keep the latest progress")
@ -1605,7 +1589,7 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
);
}
ImaginateStatus::Terminating => {
widgets.extend_from_slice(&assist_separators);
add_blank_assist(&mut widgets);
widgets.push(
TextButton::new("Terminating...")
.tooltip("Waiting on the final image generated after termination")
@ -1755,7 +1739,7 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
resolution_index,
))
.widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(),
NumberInput::new(Some(vec2.x))
.label("W")
.min(64.)

View File

@ -47,12 +47,10 @@ impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPane
let options_bar = vec![LayoutGroup::Row {
widgets: vec![
IconLabel::new("File").tooltip("Document").widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(),
TextInput::new(document_name)
.on_update(|text_input| DocumentMessage::RenameDocument { new_name: text_input.value.clone() }.into())
.widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(),
PopoverButton::new("Additional Options", "Coming soon").widget_holder(),
],
}];

View File

@ -55,10 +55,6 @@ pub enum PortfolioMessage {
data: Vec<u8>,
is_default: bool,
},
GraphViewOverlay {
open: bool,
},
GraphViewOverlayToggle,
ImaginateCheckServerStatus,
ImaginatePollServerStatus,
ImaginatePreferences,

View File

@ -3,7 +3,6 @@ use crate::application::generate_uuid;
use crate::consts::{DEFAULT_DOCUMENT_NAME, GRAPHITE_DOCUMENT_VERSION};
use crate::messages::dialog::simple_dialogs;
use crate::messages::frontend::utility_types::FrontendDocumentDetails;
use crate::messages::input_mapper::utility_types::macros::action_keys;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT};
use crate::messages::portfolio::document::DocumentInputs;
@ -22,7 +21,6 @@ pub struct PortfolioMessageHandler {
documents: HashMap<DocumentId, DocumentMessageHandler>,
document_ids: Vec<DocumentId>,
active_document_id: Option<DocumentId>,
graph_view_overlay_open: bool,
copy_buffer: [Vec<CopyBufferEntry>; INTERNAL_CLIPBOARD_COUNT as usize],
pub persistent_data: PersistentData,
pub executor: NodeGraphExecutor,
@ -55,7 +53,6 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
ipp,
persistent_data: &self.persistent_data,
executor: &mut self.executor,
graph_view_overlay_open: self.graph_view_overlay_open,
};
document.process_message(message, responses, document_inputs)
}
@ -71,7 +68,6 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
ipp,
persistent_data: &self.persistent_data,
executor: &mut self.executor,
graph_view_overlay_open: self.graph_view_overlay_open,
};
document.process_message(message, responses, document_inputs)
}
@ -257,29 +253,6 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
responses.add(BroadcastEvent::DocumentIsDirty);
}
}
PortfolioMessage::GraphViewOverlay { open } => {
self.graph_view_overlay_open = open;
let layout = WidgetLayout::new(vec![LayoutGroup::Row {
widgets: vec![IconButton::new(if open { "GraphViewOpen" } else { "GraphViewClosed" }, 32)
.tooltip(if open { "Hide Node Graph" } else { "Show Node Graph" })
.tooltip_shortcut(action_keys!(PortfolioMessageDiscriminant::GraphViewOverlayToggle))
.on_update(move |_| PortfolioMessage::GraphViewOverlay { open: !open }.into())
.widget_holder()],
}]);
responses.add(LayoutMessage::SendLayout {
layout: Layout::WidgetLayout(layout),
layout_target: LayoutTarget::GraphViewOverlayButton,
});
if open {
responses.add(NodeGraphMessage::SendGraph { should_rerender: false });
}
responses.add(FrontendMessage::TriggerGraphViewOverlay { open });
}
PortfolioMessage::GraphViewOverlayToggle => {
responses.add(PortfolioMessage::GraphViewOverlay { open: !self.graph_view_overlay_open });
}
PortfolioMessage::ImaginateCheckServerStatus => {
let server_status = self.persistent_data.imaginate.server_status().clone();
self.persistent_data.imaginate.poll_server_check();
@ -473,9 +446,6 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
responses.add(PortfolioMessage::UpdateDocumentWidgets);
responses.add(NavigationMessage::TranslateCanvas { delta: (0., 0.).into() });
responses.add(NodeGraphMessage::RunDocumentGraph);
if self.graph_view_overlay_open {
responses.add(NodeGraphMessage::SendGraph { should_rerender: false });
}
}
PortfolioMessage::SetActiveDocument { document_id } => {
self.active_document_id = Some(document_id);
@ -553,7 +523,6 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
fn actions(&self) -> ActionList {
let mut common = actions!(PortfolioMessageDiscriminant;
GraphViewOverlayToggle,
CloseActiveDocumentWithConfirmation,
CloseAllDocuments,
CloseAllDocumentsWithConfirmation,
@ -565,11 +534,6 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
ToggleRulers,
);
if self.graph_view_overlay_open {
let escape = actions!(PortfolioMessageDiscriminant; GraphViewOverlay);
common.extend(escape);
}
if let Some(document) = self.active_document() {
if document.metadata().selected_layers().next().is_some() {
let select = actions!(PortfolioMessageDiscriminant;
@ -578,7 +542,8 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
);
common.extend(select);
}
common.extend(document.actions_with_graph_open(self.graph_view_overlay_open));
common.extend(document.actions_with_graph_open());
}
common
@ -652,7 +617,6 @@ impl PortfolioMessageHandler {
responses.add(PortfolioMessage::SelectDocument { document_id });
responses.add(PortfolioMessage::LoadDocumentResources { document_id });
responses.add(PortfolioMessage::UpdateDocumentWidgets);
responses.add(PortfolioMessage::GraphViewOverlay { open: self.graph_view_overlay_open });
responses.add(ToolMessage::InitTools);
responses.add(NodeGraphMessage::Init);
responses.add(NavigationMessage::TranslateCanvas { delta: (0., 0.).into() });

View File

@ -141,7 +141,7 @@ impl LayoutHolder for BrushTool {
.widget_holder(),
];
widgets.push(Separator::new(SeparatorType::Section).widget_holder());
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
let draw_mode_entries: Vec<_> = [DrawMode::Draw, DrawMode::Erase, DrawMode::Restore]
.into_iter()
@ -149,7 +149,7 @@ impl LayoutHolder for BrushTool {
.collect();
widgets.push(RadioInput::new(draw_mode_entries).selected_index(Some(self.options.draw_mode as u32)).widget_holder());
widgets.push(Separator::new(SeparatorType::Section).widget_holder());
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
widgets.append(&mut self.options.color.create_widgets(
"Color",

View File

@ -96,7 +96,7 @@ impl LayoutHolder for EllipseTool {
|color: &ColorButton| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::FillColor(color.value)).into(),
);
widgets.push(Separator::new(SeparatorType::Section).widget_holder());
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
widgets.append(&mut self.options.stroke.create_widgets(
"Stroke",

View File

@ -103,7 +103,7 @@ impl LayoutHolder for FreehandTool {
|color: &ColorButton| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::FillColor(color.value)).into(),
);
widgets.push(Separator::new(SeparatorType::Section).widget_holder());
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
widgets.append(&mut self.options.stroke.create_widgets(
"Stroke",

View File

@ -118,7 +118,7 @@ impl LayoutHolder for PenTool {
|color: &ColorButton| PenToolMessage::UpdateOptions(PenOptionsUpdate::FillColor(color.value)).into(),
);
widgets.push(Separator::new(SeparatorType::Section).widget_holder());
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
widgets.append(&mut self.options.stroke.create_widgets(
"Stroke",

View File

@ -125,7 +125,7 @@ impl LayoutHolder for PolygonTool {
create_sides_widget(self.options.vertices),
];
widgets.push(Separator::new(SeparatorType::Section).widget_holder());
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
widgets.append(&mut self.options.fill.create_widgets(
"Fill",
@ -135,7 +135,7 @@ impl LayoutHolder for PolygonTool {
|color: &ColorButton| PolygonToolMessage::UpdateOptions(PolygonOptionsUpdate::FillColor(color.value)).into(),
));
widgets.push(Separator::new(SeparatorType::Section).widget_holder());
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
widgets.append(&mut self.options.stroke.create_widgets(
"Stroke",

View File

@ -84,7 +84,7 @@ impl LayoutHolder for RectangleTool {
|color: &ColorButton| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::FillColor(color.value)).into(),
);
widgets.push(Separator::new(SeparatorType::Section).widget_holder());
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
widgets.append(&mut self.options.stroke.create_widgets(
"Stroke",

View File

@ -175,23 +175,20 @@ impl LayoutHolder for SelectTool {
// Align
let disabled = self.tool_data.selected_layers_count < 2;
widgets.push(Separator::new(SeparatorType::Section).widget_holder());
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
widgets.extend(self.alignment_widgets(disabled));
widgets.push(Separator::new(SeparatorType::Related).widget_holder());
widgets.push(PopoverButton::new("Align", "Coming soon").disabled(disabled).widget_holder());
// Flip
let disabled = self.tool_data.selected_layers_count == 0;
widgets.push(Separator::new(SeparatorType::Section).widget_holder());
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
widgets.extend(self.flip_widgets(disabled));
widgets.push(Separator::new(SeparatorType::Related).widget_holder());
widgets.push(PopoverButton::new("Flip", "Coming soon").disabled(disabled).widget_holder());
// Boolean
if self.tool_data.selected_layers_count >= 2 {
widgets.push(Separator::new(SeparatorType::Section).widget_holder());
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
widgets.extend(self.boolean_widgets());
widgets.push(Separator::new(SeparatorType::Related).widget_holder());
widgets.push(PopoverButton::new("Boolean", "Coming soon").widget_holder());
}

View File

@ -105,7 +105,7 @@ impl LayoutHolder for SplineTool {
|color: &ColorButton| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::FillColor(color.value)).into(),
);
widgets.push(Separator::new(SeparatorType::Section).widget_holder());
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
widgets.append(&mut self.options.stroke.create_widgets(
"Stroke",

View File

@ -130,7 +130,7 @@ impl LayoutHolder for TextTool {
fn layout(&self) -> Layout {
let mut widgets = create_text_widgets(self);
widgets.push(Separator::new(SeparatorType::Section).widget_holder());
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
widgets.append(&mut self.options.fill.create_widgets(
"Fill",

View File

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

View File

@ -189,11 +189,6 @@
display: block;
}
.sharp-right-corners.sharp-right-corners.sharp-right-corners.sharp-right-corners {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.layout-row,
.layout-col {
.scrollable-x,
@ -265,7 +260,7 @@
.popover-button,
.color-button > button,
.color-picker .preset-color,
.working-colors-button .swatch > button,
.working-colors-input .swatch > button,
.radio-input button,
.menu-list,
.menu-list-button .entry,

View File

@ -537,7 +537,7 @@
width: 208px;
height: 32px;
border-radius: 2px;
border: 1px solid var(--color-0-black);
border: 1px solid var(--color-1-nearblack);
box-sizing: border-box;
overflow: hidden;

View File

@ -41,13 +41,13 @@
<div class="widget-layout details">
<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"><TextButton label="Report Bug" icon="Warning" flush={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"
noBackground={true}
flush={true}
action={async () => {
await wipeDocuments();
window.location.reload();

View File

@ -212,7 +212,7 @@
{/if}
{#each entries as section, sectionIndex (sectionIndex)}
{#if sectionIndex > 0}
<Separator type="List" direction="Vertical" />
<Separator type="Section" direction="Vertical" />
{/if}
{#each virtualScrollingEntryHeight ? section.slice(virtualScrollingStartIndex, virtualScrollingEndIndex) : section as entry, entryIndex (entryIndex + startIndex)}
<LayoutRow
@ -264,8 +264,12 @@
.floating-menu-container .floating-menu-content.floating-menu-content {
padding: 4px 0;
.separator div {
background: var(--color-4-dimgray);
.separator {
margin: 4px 0;
div {
background: var(--color-4-dimgray);
}
}
.scroll-spacer {
@ -333,7 +337,7 @@
&:hover,
&.open {
background: var(--color-5-dullgray);
background: var(--color-4-dimgray);
}
&.active {

View File

@ -450,8 +450,7 @@
<LayoutRow class="spacer" />
{/if}
<LayoutCol class="shelf-bottom-widgets">
<WidgetLayout class={"graph-overlay-button-area"} layout={$document.graphViewOverlayButtonLayout} />
<WidgetLayout class={"working-colors-button-area"} layout={$document.workingColorsLayout} />
<WidgetLayout class={"working-colors-input-area"} layout={$document.workingColorsLayout} />
</LayoutCol>
</LayoutCol>
<LayoutCol class="table">
@ -519,6 +518,10 @@
.document {
height: 100%;
&.document.document {
padding-bottom: 0;
}
.options-bar {
height: 32px;
flex: 0 0 auto;
@ -537,12 +540,14 @@
// Enables usage of the `100cqh` unit to reference the height of this container element.
container-type: size;
// Be sure to recalculate this if the items below the tools (working colors and graph overlay buttons) change height in the future.
--height-of-elements-below-tools: 104px;
--height-of-elements-below-tools: 64px;
// Target height for the tools within the container above the lower elements.
--available-height: calc(100cqh - var(--height-of-elements-below-tools));
// Be sure to update this if the height changes as set in `Separator.svelte`.
--separator-height: calc(12px + 1px + 12px);
// The least height required to fit all the tools in 1 column and 2 columns, which the available space must exceed in order for the fewest number of columns to be used.
--1-col-required-height: calc(var(--total-tool-rows-for-1-columns) * 32px + var(--total-separators) * (1px + 8px * 2));
--2-col-required-height: calc(var(--total-tool-rows-for-2-columns) * 32px + var(--total-separators) * (1px + 8px * 2));
--1-col-required-height: calc(var(--total-tool-rows-for-1-columns) * 32px + var(--total-separators) * var(--separator-height));
--2-col-required-height: calc(var(--total-tool-rows-for-2-columns) * 32px + var(--total-separators) * var(--separator-height));
// Evaluates to 0px (if false) or 1px (if true). We multiply by 1000000 to force the result to be an integer 0 or 1 and not interpolate values in-between.
--needs-at-least-2-columns: calc(1px - clamp(0px, calc((var(--available-height) - Min(var(--available-height), var(--1-col-required-height))) * 1000000), 1px));
--needs-at-least-3-columns: calc(1px - clamp(0px, calc((var(--available-height) - Min(var(--available-height), var(--2-col-required-height))) * 1000000), 1px));
@ -563,7 +568,7 @@
// Remove this when the Firefox bug is fixed.
@-moz-document url-prefix() {
--available-height-plus-1: calc(var(--available-height) + 1px);
--3-col-required-height: calc(var(--total-tool-rows-for-3-columns) * 32px + var(--total-separators) * (1px + 8px * 2));
--3-col-required-height: calc(var(--total-tool-rows-for-3-columns) * 32px + var(--total-separators) * var(--separator-height));
--overflows-with-3-columns: calc(1px - clamp(0px, calc((var(--available-height-plus-1) - Min(var(--available-height-plus-1), var(--3-col-required-height))) * 1000000), 1px));
--firefox-scrollbar-width-space-occupied: 8; // Might change someday, or on different platforms, but this is the value in FF 120 on Windows
padding-right: calc(var(--firefox-scrollbar-width-space-occupied) * var(--overflows-with-3-columns));
@ -610,17 +615,12 @@
flex: 0 0 auto;
align-items: center;
.graph-overlay-button-area {
height: auto;
align-items: center;
}
.working-colors-button-area {
.working-colors-input-area {
height: auto;
margin: 0;
min-height: 0;
.working-colors-button {
.working-colors-input {
margin: 0;
}

View File

@ -505,7 +505,6 @@
&::placeholder {
opacity: 1;
color: inherit;
font-style: italic;
}
}
}

View File

@ -51,6 +51,16 @@
.sections {
flex: 1 1 100%;
// Used as a placeholder for empty assist widgets
.separator.section.horizontal {
margin: 0;
margin-left: 24px;
div {
width: 0;
}
}
}
.text-button {

View File

@ -741,7 +741,7 @@
</div>
<div class="details">
<!-- TODO: Allow the user to edit the name, just like in the Layers panel -->
<TextLabel tooltip={editor.instance.inDevelopmentMode() ? `Node ID: ${node.id}` : undefined} italic={!node.alias}>{node.alias || "Layer"}</TextLabel>
<TextLabel tooltip={editor.instance.inDevelopmentMode() ? `Node ID: ${node.id}` : undefined}>{node.alias || "Layer"}</TextLabel>
</div>
<svg class="border-mask" width="0" height="0">
@ -773,14 +773,13 @@
<div class="primary" class:no-parameter-section={exposedInputsOutputs.length === 0}>
<IconLabel icon={nodeIcon(node.name)} />
<!-- TODO: Allow the user to edit the name, just like in the Layers panel -->
<TextLabel tooltip={editor.instance.inDevelopmentMode() ? `Node ID: ${node.id}` : undefined} italic={!node.alias}>{node.alias || node.name}</TextLabel>
<TextLabel tooltip={editor.instance.inDevelopmentMode() ? `Node ID: ${node.id}` : undefined}>{node.alias || node.name}</TextLabel>
</div>
<!-- Parameter rows -->
{#if exposedInputsOutputs.length > 0}
<div class="parameters">
{#each exposedInputsOutputs as parameter, index}
<div class={`parameter expanded ${index < node.exposedInputs.length ? "input" : "output"}`}>
<div class="expand-arrow" />
<TextLabel tooltip={parameter.name}>{parameter.name}</TextLabel>
</div>
{/each}
@ -1005,10 +1004,6 @@
.icon-label {
fill: var(--color-a-softgray);
}
.expand-arrow::after {
background: var(--icon-expand-collapse-arrow-disabled);
}
}
&.previewed::after {
@ -1035,34 +1030,6 @@
height: 8px;
}
.expand-arrow {
width: 16px;
height: 16px;
margin: 0;
padding: 0;
position: relative;
flex: 0 0 auto;
display: flex;
align-items: center;
justify-content: center;
&::after {
content: "";
position: absolute;
width: 8px;
height: 8px;
background: var(--icon-expand-collapse-arrow);
}
&:hover::after {
background: var(--icon-expand-collapse-arrow-hover);
}
}
.expanded .expand-arrow::after {
transform: rotate(90deg);
}
.text-label {
overflow: hidden;
text-overflow: ellipsis;
@ -1201,8 +1168,8 @@
}
.text-label {
margin-left: 8px; // Remove after reenabling icon-label
margin-right: 4px;
// margin-right: 4px; // Restore after reenabling icon-label
margin: 0 8px;
}
}
@ -1216,7 +1183,8 @@
position: relative;
display: flex;
align-items: center;
width: 100%;
margin: 0 8px;
width: calc(100% - 8px - 8px);
height: 24px;
&:last-of-type {
@ -1227,19 +1195,10 @@
width: 100%;
}
&.input {
.expand-arrow {
margin-left: 4px;
}
}
&.output {
flex-direction: row-reverse;
text-align: right;
.expand-arrow {
margin-right: 4px;
}
svg {
width: 30px;
height: 20px;

View File

@ -44,6 +44,10 @@
flex: 0 0 auto;
margin: 0 4px;
+ .widget-section {
margin-top: 4px;
}
.header {
text-align: left;
align-items: center;
@ -107,7 +111,6 @@
padding: 0 7px;
padding-top: 1px;
margin-top: -1px;
margin-bottom: 4px;
border: 1px solid var(--color-2-mildblack);
border-radius: 0 0 4px 4px;
overflow: hidden;
@ -130,8 +133,8 @@
margin-left: 16px;
}
> .parameter-expose-button ~ .text-label:first-of-type {
margin-left: 0;
> .parameter-expose-button + .text-label:first-of-type {
margin-left: 8px;
}
> .text-button {

View File

@ -12,25 +12,22 @@
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";
import FontInput from "@graphite/components/widgets/inputs/FontInput.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 TextAreaInput from "@graphite/components/widgets/inputs/TextAreaInput.svelte";
import TextInput from "@graphite/components/widgets/inputs/TextInput.svelte";
import WorkingColorsInput from "@graphite/components/widgets/inputs/WorkingColorsInput.svelte";
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
import ImageLabel from "@graphite/components/widgets/labels/ImageLabel.svelte";
import Separator from "@graphite/components/widgets/labels/Separator.svelte";
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
const SUFFIX_WIDGETS = ["PopoverButton"];
const editor = getContext<Editor>("editor");
export let widgetData: WidgetSpanRow | WidgetSpanColumn;
@ -47,7 +44,6 @@
$: direction = watchDirection(widgetData);
$: widgets = watchWidgets(widgetData);
$: widgetsAndNextSiblingIsSuffix = watchWidgetsAndNextSiblingIsSuffix(widgets);
function watchDirection(widgetData: WidgetSpanRow | WidgetSpanColumn): "row" | "column" | undefined {
if (isWidgetSpanRow(widgetData)) return "row";
@ -61,17 +57,6 @@
return widgets;
}
function watchWidgetsAndNextSiblingIsSuffix(widgets: Widget[]): [Widget, boolean][] {
return widgets.map((widget, index): [Widget, boolean] => {
// A suffix widget is one that joins up with this widget at the end with only a 1px gap.
// It uses the CSS sibling selector to give its own left edge corners zero radius.
// But this JS is needed to set its preceding sibling widget's right edge corners to zero radius.
const nextSiblingIsSuffix = SUFFIX_WIDGETS.includes(widgets[index + 1]?.props.kind);
return [widget, nextSiblingIsSuffix];
});
}
function updateLayout(index: number, value: unknown) {
editor.instance.updateLayout(layoutTarget, widgets[index].widgetId, value);
}
@ -88,14 +73,14 @@
<!-- TODO: Refactor this component to use `<svelte:component this={attributesObject} />` to avoid all the separate conditional components -->
<div class={`widget-span ${className} ${extraClasses}`.trim()} class:row={direction === "row"} class:column={direction === "column"}>
{#each widgetsAndNextSiblingIsSuffix as [component, nextIsSuffix], index}
{#each widgets as component, index}
{@const checkboxInput = narrowWidgetProps(component.props, "CheckboxInput")}
{#if checkboxInput}
<CheckboxInput {...exclude(checkboxInput)} on:checked={({ detail }) => updateLayout(index, detail)} />
{/if}
{@const colorInput = narrowWidgetProps(component.props, "ColorButton")}
{#if colorInput}
<ColorButton {...exclude(colorInput)} on:value={({ detail }) => updateLayout(index, detail)} sharpRightCorners={nextIsSuffix} />
<ColorButton {...exclude(colorInput)} on:value={({ detail }) => updateLayout(index, detail)} />
{/if}
{@const curvesInput = narrowWidgetProps(component.props, "CurveInput")}
{#if curvesInput}
@ -103,11 +88,11 @@
{/if}
{@const dropdownInput = narrowWidgetProps(component.props, "DropdownInput")}
{#if dropdownInput}
<DropdownInput {...exclude(dropdownInput)} on:selectedIndex={({ detail }) => updateLayout(index, detail)} sharpRightCorners={nextIsSuffix} />
<DropdownInput {...exclude(dropdownInput)} on:selectedIndex={({ detail }) => updateLayout(index, detail)} />
{/if}
{@const fontInput = narrowWidgetProps(component.props, "FontInput")}
{#if fontInput}
<FontInput {...exclude(fontInput)} on:changeFont={({ detail }) => updateLayout(index, detail)} sharpRightCorners={nextIsSuffix} />
<FontInput {...exclude(fontInput)} on:changeFont={({ detail }) => updateLayout(index, detail)} />
{/if}
{@const parameterExposeButton = narrowWidgetProps(component.props, "ParameterExposeButton")}
{#if parameterExposeButton}
@ -115,7 +100,7 @@
{/if}
{@const iconButton = narrowWidgetProps(component.props, "IconButton")}
{#if iconButton}
<IconButton {...exclude(iconButton)} action={() => updateLayout(index, undefined)} sharpRightCorners={nextIsSuffix} />
<IconButton {...exclude(iconButton)} action={() => updateLayout(index, undefined)} />
{/if}
{@const iconLabel = narrowWidgetProps(component.props, "IconLabel")}
{#if iconLabel}
@ -132,13 +117,8 @@
on:value={({ detail }) => debouncer((value) => updateLayout(index, value)).debounceUpdateValue(detail)}
incrementCallbackIncrease={() => updateLayout(index, "Increment")}
incrementCallbackDecrease={() => updateLayout(index, "Decrement")}
sharpRightCorners={nextIsSuffix}
/>
{/if}
{@const optionalInput = narrowWidgetProps(component.props, "OptionalInput")}
{#if optionalInput}
<OptionalInput {...exclude(optionalInput)} on:checked={({ detail }) => updateLayout(index, detail)} />
{/if}
{@const pivotInput = narrowWidgetProps(component.props, "PivotInput")}
{#if pivotInput}
<PivotInput {...exclude(pivotInput)} on:position={({ detail }) => updateLayout(index, detail)} />
@ -156,15 +136,15 @@
{/if}
{@const radioInput = narrowWidgetProps(component.props, "RadioInput")}
{#if radioInput}
<RadioInput {...exclude(radioInput)} on:selectedIndex={({ detail }) => updateLayout(index, detail)} sharpRightCorners={nextIsSuffix} />
<RadioInput {...exclude(radioInput)} on:selectedIndex={({ detail }) => updateLayout(index, detail)} />
{/if}
{@const separator = narrowWidgetProps(component.props, "Separator")}
{#if separator}
<Separator {...exclude(separator)} />
{/if}
{@const workingColorsButton = narrowWidgetProps(component.props, "WorkingColorsButton")}
{#if workingColorsButton}
<WorkingColorsButton {...exclude(workingColorsButton)} />
{@const workingColorsInput = narrowWidgetProps(component.props, "WorkingColorsInput")}
{#if workingColorsInput}
<WorkingColorsInput {...exclude(workingColorsInput)} />
{/if}
{@const textAreaInput = narrowWidgetProps(component.props, "TextAreaInput")}
{#if textAreaInput}
@ -172,7 +152,7 @@
{/if}
{@const textButton = narrowWidgetProps(component.props, "TextButton")}
{#if textButton}
<TextButton {...exclude(textButton)} action={() => updateLayout(index, undefined)} sharpRightCorners={nextIsSuffix} />
<TextButton {...exclude(textButton)} action={() => updateLayout(index, undefined)} />
{/if}
{@const breadcrumbTrailButtons = narrowWidgetProps(component.props, "BreadcrumbTrailButtons")}
{#if breadcrumbTrailButtons}
@ -180,7 +160,7 @@
{/if}
{@const textInput = narrowWidgetProps(component.props, "TextInput")}
{#if textInput}
<TextInput {...exclude(textInput)} on:commitText={({ detail }) => updateLayout(index, detail)} sharpRightCorners={nextIsSuffix} />
<TextInput {...exclude(textInput)} on:commitText={({ detail }) => updateLayout(index, detail)} />
{/if}
{@const textLabel = narrowWidgetProps(component.props, "TextLabel")}
{#if textLabel}
@ -218,31 +198,6 @@
--widget-height: 16px;
}
}
// TODO: Target this in a better way than using the tooltip, which will break if changed, or when localized/translated
.checkbox-input [title="Preserve Aspect Ratio"] {
margin-bottom: -32px;
position: relative;
&::before,
&::after {
content: "";
pointer-events: none;
position: absolute;
left: 8px;
width: 1px;
height: 16px;
background: var(--color-7-middlegray);
}
&::before {
top: calc(-4px - 16px);
}
&::after {
bottom: calc(-4px - 16px);
}
}
}
// paddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpadding
</style>

View File

@ -16,10 +16,9 @@
export let allowNone = false;
// export let allowTransparency = false; // TODO: Implement
export let tooltip: string | undefined = undefined;
export let sharpRightCorners = false;
</script>
<LayoutCol class="color-button" classes={{ disabled, none: value.none, open, "sharp-right-corners": sharpRightCorners }} {tooltip}>
<LayoutCol class="color-button" classes={{ disabled, none: value.none, open }} {tooltip}>
<button {disabled} style:--chosen-color={value.toHexOptionalAlpha()} on:click={() => (open = true)} tabindex="0" data-floating-menu-spawner></button>
{#if disabled && !value.none}
<TextLabel>sRGB</TextLabel>
@ -59,11 +58,6 @@
}
}
&.sharp-right-corners {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
> button {
border: none;
padding: 0;

View File

@ -8,7 +8,6 @@
export let disabled = false;
export let active = false;
export let tooltip: string | undefined = undefined;
export let sharpRightCorners = false;
// Callbacks
export let action: (e?: MouseEvent) => void;
@ -21,17 +20,7 @@
.join(" ");
</script>
<button
class={`icon-button size-${size} ${className} ${extraClasses}`.trim()}
class:disabled
class:active
class:sharp-right-corners={sharpRightCorners}
on:click={action}
{disabled}
title={tooltip}
tabindex={active ? -1 : 0}
{...$$restProps}
>
<button class={`icon-button size-${size} ${className} ${extraClasses}`.trim()} class:disabled class:active on:click={action} {disabled} title={tooltip} tabindex={active ? -1 : 0} {...$$restProps}>
<IconLabel {icon} />
</button>

View File

@ -9,7 +9,22 @@
</script>
<LayoutRow class="parameter-expose-button">
<button class:exposed style:--data-type-color={`var(--color-data-${dataType})`} on:click={action} title={tooltip} tabindex="-1" />
<button class:exposed style:--data-type-color={`var(--color-data-${dataType})`} on:click={action} title={tooltip} tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10">
<path class="interior" d="M0,7.882c0,1.832,1.325,2.63,2.945,1.772L8.785,6.56c1.62-.858,1.62-2.262,0-3.12L2.945.345C1.325-.512,0,.285,0,2.118Z" />
<path
class="outline"
d="M 1.705180287361145 9.999852180480957 L 1.705180287361145 8.999852180480957 C 1.9275803565979 8.999852180480957 2.194530248641968 8.920772552490234 2.476730346679688
8.771392822265625 L 8.31682014465332 5.67636251449585 C 8.788760185241699 5.426312446594238 9 5.156492233276367 9 5.000002384185791 C 9 4.843512535095215 8.788760185241699
4.573692321777344 8.316730499267578 4.323602199554443 L 2.477190256118774 1.228852391242981 C 2.194520235061646 1.079232335090637 1.927510380744934 1.000152349472046 1.70503032207489
1.000152349472046 C 1.091590285301208 1.000152349472046 1.000000357627869 1.700212359428406 1.000000357627869 2.117512464523315 L 1.000000357627869 7.882492542266846 C
1.000000357627869 8.299762725830078 1.091610312461853 8.999792098999023 1.705130338668823 8.999852180480957 L 1.705180287361145 9.999852180480957 M 1.705027341842651 9.999849319458008
C 0.7003514766693115 9.999751091003418 0 9.214582443237305 0 7.882492542266846 L 0 2.117512464523315 C 0 0.2850223779678345 1.325000405311584 -0.512467622756958 2.945000410079956
0.3450223803520203 L 8.785000801086426 3.440012454986572 C 10.40500068664551 4.298342227935791 10.40500068664551 5.701662540435791 8.785000801086426 6.55999231338501 L
2.945000410079956 9.654982566833496 C 2.502624750137329 9.889138221740723 2.082434415817261 9.999885559082031 1.705027341842651 9.999849319458008 Z"
/>
</svg>
</button>
</LayoutRow>
<style lang="scss" global>
@ -26,22 +41,38 @@
margin: 0;
padding: 0;
border: none;
border-radius: 50%;
background: none;
fill: none;
stroke: none;
svg {
width: 10px;
height: 10px;
margin-top: -1px;
margin-left: -1px;
}
&:not(.exposed) {
background: none;
border: 1px solid var(--data-type-color);
.outline {
fill: var(--data-type-color);
}
&:hover {
background: var(--color-6-lowergray);
.interior {
fill: var(--color-6-lowergray);
}
}
}
&.exposed {
background: var(--data-type-color);
.interior {
fill: var(--data-type-color);
}
&:hover {
border: 1px solid var(--color-f-white);
.outline {
fill: var(--color-f-white);
}
}
}
}

View File

@ -1,11 +1,13 @@
<script lang="ts">
import type { IconName } from "@graphite/utility-functions/icons";
import { type IconName, type PopoverButtonStyle } from "@graphite/utility-functions/icons";
import FloatingMenu from "@graphite/components/layout/FloatingMenu.svelte";
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte";
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
export let icon: IconName = "DropdownArrow";
export let style: PopoverButtonStyle = "DropdownArrow";
export let icon: IconName | undefined = undefined;
export let tooltip: string | undefined = undefined;
export let disabled = false;
@ -20,8 +22,11 @@
}
</script>
<LayoutRow class="popover-button">
<IconButton classes={{ open }} {disabled} action={() => onClick()} icon={icon || "DropdownArrow"} size={16} {tooltip} data-floating-menu-spawner />
<LayoutRow class="popover-button" classes={{ "has-icon": icon !== undefined }}>
<IconButton class="dropdown-icon" classes={{ open }} {disabled} action={() => onClick()} icon={style || "DropdownArrow"} size={16} {tooltip} data-floating-menu-spawner />
{#if icon !== undefined}
<IconLabel class="descriptive-icon" classes={{ open }} {disabled} {icon} {tooltip} />
{/if}
<FloatingMenu {open} on:open={({ detail }) => (open = detail)} type="Popover" direction="Bottom">
<slot />
@ -35,38 +40,40 @@
height: 24px;
flex: 0 0 auto;
.floating-menu {
left: 50%;
bottom: 0;
&.has-icon {
width: 36px;
.dropdown-icon {
padding-left: calc(36px - 16px);
box-sizing: content-box;
}
}
.icon-button.icon-button {
width: 100%;
.dropdown-icon {
width: 16px;
height: 100%;
padding: 0;
border: none;
border-radius: 2px;
background: var(--color-1-nearblack);
fill: var(--color-e-nearwhite);
&:hover,
&.open {
&:hover:not(.disabled),
&.open:not(.disabled) {
background: var(--color-5-dullgray);
}
&.disabled {
background: var(--color-2-mildblack);
fill: var(--color-8-uppergray);
}
}
// TODO: Refactor this and other complicated cases dealing with joined widget margins and border-radius by adding a single standard set of classes: joined-first, joined-inner, and joined-last
div[class*="-input"] + & {
margin-left: 1px;
.descriptive-icon {
width: 16px;
height: 16px;
margin: auto 0;
margin-left: calc(-16px - 16px);
pointer-events: none;
}
.icon-button {
border-radius: 0 2px 2px 0;
}
.floating-menu {
left: 50%;
bottom: 0;
}
}
</style>

View File

@ -15,11 +15,10 @@
export let label: string;
export let icon: IconName | undefined = undefined;
export let emphasized = false;
export let noBackground = false;
export let flush = false;
export let minWidth = 0;
export let disabled = false;
export let tooltip: string | undefined = undefined;
export let sharpRightCorners = false;
export let menuListChildren: MenuListEntry[][] | undefined = undefined;
// Callbacks
@ -57,8 +56,7 @@
class:open={self?.open}
class:emphasized
class:disabled
class:no-background={noBackground}
class:sharp-right-corners={sharpRightCorners}
class:flush
style:min-width={minWidth > 0 ? `${minWidth}px` : ""}
title={tooltip}
data-emphasized={emphasized || undefined}
@ -73,7 +71,7 @@
<IconLabel {icon} />
{/if}
{#if icon && label}
<Separator type={noBackground ? "Unrelated" : "Related"} />
<Separator type={flush ? "Unrelated" : "Related"} />
{/if}
{#if label}
<TextLabel>{label}</TextLabel>
@ -140,7 +138,7 @@
}
}
&.no-background {
&.flush {
background: none;
&:hover,

View File

@ -218,7 +218,7 @@
max-width: calc(8 * var(--widget-height));
.grid {
stroke: var(--color-7-middlegray);
stroke: var(--color-5-dullgray);
stroke-width: 0.005;
pointer-events: none;
}
@ -251,7 +251,7 @@
}
.handle-line {
stroke: var(--color-7-middlegray);
stroke: var(--color-5-dullgray);
stroke-width: 0.005;
pointer-events: none;
}

View File

@ -21,7 +21,6 @@
export let interactive = true;
export let disabled = false;
export let tooltip: string | undefined = undefined;
export let sharpRightCorners = false;
let activeEntry = makeActiveEntry();
let activeEntrySkipWatcher = false;
@ -64,7 +63,7 @@
<LayoutRow class="dropdown-input" bind:this={self} data-dropdown-input>
<LayoutRow
class="dropdown-box"
classes={{ disabled, open, "sharp-right-corners": sharpRightCorners }}
classes={{ disabled, open }}
styles={{ "min-width": `${minWidth}px` }}
{tooltip}
on:click={() => !disabled && (open = true)}
@ -128,7 +127,7 @@
&:hover,
&.open {
background: var(--color-5-dullgray);
background: var(--color-4-dimgray);
}
&.disabled {

View File

@ -24,7 +24,6 @@
export let disabled = false;
export let textarea = false;
export let tooltip: string | undefined = undefined;
export let sharpRightCorners = false;
export let placeholder: string | undefined = undefined;
export let hideContextMenu = false;
@ -69,7 +68,7 @@
</script>
<!-- This is a base component, extended by others like NumberInput and TextInput. It should not be used directly. -->
<LayoutRow class={`field-input ${className}`} classes={{ disabled, "sharp-right-corners": sharpRightCorners, ...classes }} style={styleName} {styles} {tooltip}>
<LayoutRow class={`field-input ${className}`} classes={{ disabled, ...classes }} style={styleName} {styles} {tooltip}>
{#if !textarea}
<input
type="text"
@ -154,14 +153,14 @@
caret-color: var(--color-e-nearwhite);
&::selection {
background-color: var(--color-5-dullgray);
background-color: var(--color-4-dimgray);
// Target only Safari
@supports (background: -webkit-named-image(i)) {
& {
// Setting an alpha value opts out of Safari's "fancy" (but not visible on dark backgrounds) selection highlight rendering
// https://stackoverflow.com/a/71753552/775283
background-color: rgba(var(--color-5-dullgray-rgb), calc(254 / 255));
background-color: rgba(var(--color-4-dimgray-rgb), calc(254 / 255));
}
}
}

View File

@ -24,7 +24,6 @@
export let isStyle = false;
export let disabled = false;
export let tooltip: string | undefined = undefined;
export let sharpRightCorners = false;
let open = false;
let entries: MenuListEntry[] = [];
@ -107,7 +106,7 @@
<LayoutRow class="font-input">
<LayoutRow
class="dropdown-box"
classes={{ disabled, "sharp-right-corners": sharpRightCorners }}
classes={{ disabled }}
styles={{ "min-width": `${minWidth}px` }}
{tooltip}
tabindex={disabled ? -1 : 0}

View File

@ -51,7 +51,6 @@
// Styling
export let minWidth = 0;
export let sharpRightCorners = false;
// Callbacks
export let incrementCallbackIncrease: (() => void) | undefined = undefined;
@ -581,7 +580,6 @@
{label}
{disabled}
{tooltip}
{sharpRightCorners}
{styles}
hideContextMenu={true}
spellcheck={false}
@ -658,7 +656,7 @@
background: none;
&:hover {
background: var(--color-5-dullgray);
background: var(--color-4-dimgray);
}
&.right {

View File

@ -1,36 +0,0 @@
<script lang="ts">
import type { IconName } from "@graphite/utility-functions/icons";
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
import CheckboxInput from "@graphite/components/widgets/inputs/CheckboxInput.svelte";
export let checked: boolean;
export let disabled = false;
export let icon: IconName = "Checkmark";
export let tooltip: string | undefined = undefined;
</script>
<LayoutRow class="optional-input" classes={{ disabled }}>
<CheckboxInput {checked} on:checked {disabled} {icon} {tooltip} />
</LayoutRow>
<style lang="scss" global>
.optional-input {
flex-grow: 0;
.checkbox-input label {
align-items: center;
justify-content: center;
white-space: nowrap;
width: 24px;
height: 24px;
border: 1px solid var(--color-5-dullgray);
border-radius: 2px 0 0 2px;
box-sizing: border-box;
}
&.disabled .checkbox-input label {
border: 1px solid var(--color-4-dimgray);
}
}
</style>

View File

@ -12,7 +12,6 @@
export let entries: RadioEntries;
export let selectedIndex: number | undefined = undefined;
export let disabled = false;
export let sharpRightCorners = false;
function handleEntryClick(radioEntryData: RadioEntryData) {
const index = entries.indexOf(radioEntryData);
@ -28,7 +27,6 @@
class:active={index === selectedIndex}
class:mixed={selectedIndex === undefined}
class:disabled
class:sharp-right-corners={index === entries.length - 1 && sharpRightCorners}
on:click={() => handleEntryClick(entry)}
title={entry.tooltip}
tabindex={index === selectedIndex ? -1 : 0}
@ -38,7 +36,7 @@
<IconLabel icon={entry.icon} />
{/if}
{#if entry.label}
<TextLabel>{entry.label}</TextLabel>
<TextLabel italic={selectedIndex === undefined}>{entry.label}</TextLabel>
{/if}
</button>
{/each}

View File

@ -135,7 +135,7 @@
path {
stroke-width: 1px;
stroke: var(--color-7-middlegray);
stroke: var(--color-6-lowergray);
}
text {

View File

@ -16,7 +16,6 @@
// Styling
export let centered = false;
export let minWidth = 0;
export let sharpRightCorners = false;
let self: FieldInput | undefined;
let editing = false;
@ -67,7 +66,6 @@
{disabled}
{tooltip}
{placeholder}
{sharpRightCorners}
bind:this={self}
/>

View File

@ -6,7 +6,7 @@
</script>
<div class={`separator ${direction.toLowerCase()} ${type.toLowerCase()}`}>
{#if ["Section", "List"].includes(type)}
{#if type === "Section"}
<div />
{/if}
</div>
@ -21,27 +21,20 @@
}
&.unrelated {
height: 8px;
}
&.section,
&.list {
width: 100%;
div {
height: 1px;
width: calc(100% - 8px);
margin: 0 4px;
background: var(--color-7-middlegray);
}
height: 16px;
}
&.section {
margin: 8px 0;
}
// If changing this, update `--separator-height` in `Document.svelte`
margin: 12px 0;
width: 100%;
&.list {
margin: 4px 0;
div {
margin: 0 4px;
height: 1px;
width: calc(100% - 8px);
background: var(--color-5-dullgray);
}
}
}
@ -53,27 +46,19 @@
}
&.unrelated {
width: 8px;
}
&.section,
&.list {
height: 100%;
div {
height: calc(100% - 8px);
width: 1px;
margin: 4px 0;
background: var(--color-7-middlegray);
}
width: 16px;
}
&.section {
margin: 0 8px;
}
margin: 0 12px;
height: 100%;
&.list {
margin: 0 4px;
div {
margin: 4px 0;
height: calc(100% - 8px);
width: 1px;
background: var(--color-5-dullgray);
}
}
}
}

View File

@ -248,11 +248,11 @@
.floating-menu-content .row:hover > & {
.input-key {
border-color: var(--color-7-middlegray);
border-color: var(--color-8-uppergray);
}
.input-mouse .dim {
fill: var(--color-7-middlegray);
fill: var(--color-8-uppergray);
}
}
}

View File

@ -57,7 +57,8 @@
overflow: hidden;
.separator.section {
margin: 0;
// Width of section separator (12px) minus the margin of the surrounding user input labels (8px)
margin: 0 calc(12px - 8px);
}
.plus,

View File

@ -77,7 +77,7 @@
<WindowButtonsMac {maximized} />
{:else}
{#each entries as entry}
<TextButton label={entry.label} icon={entry.icon} menuListChildren={entry.children} action={entry.action} noBackground={true} />
<TextButton label={entry.label} icon={entry.icon} menuListChildren={entry.children} action={entry.action} flush={true} />
{/each}
{/if}
</LayoutRow>

View File

@ -3,7 +3,6 @@
import Layers from "@graphite/components/panels/Layers.svelte";
import Properties from "@graphite/components/panels/Properties.svelte";
import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte";
import PopoverButton from "@graphite/components/widgets/buttons/PopoverButton.svelte";
import TextButton from "@graphite/components/widgets/buttons/TextButton.svelte";
const PANEL_COMPONENTS = {
@ -102,10 +101,10 @@
</LayoutRow>
{/each}
</LayoutRow>
<PopoverButton icon="VerticalEllipsis">
<!-- <PopoverButton style="VerticalEllipsis">
<TextLabel bold={true}>Panel Options</TextLabel>
<TextLabel multiline={true}>Coming soon</TextLabel>
</PopoverButton>
</PopoverButton> -->
</LayoutRow>
<LayoutCol class="panel-body">
{#if panelType}
@ -120,7 +119,7 @@
<table>
<tr>
<td>
<TextButton label="New Document" icon="File" noBackground={true} action={() => editor.instance.newDocumentDialog()} />
<TextButton label="New Document" icon="File" flush={true} action={() => editor.instance.newDocumentDialog()} />
</td>
<td>
<UserInputLabel keysWithLabelsGroups={[[...platformModifiers(true), { key: "KeyN", label: "N" }]]} />
@ -128,7 +127,7 @@
</tr>
<tr>
<td>
<TextButton label="Open Document" icon="Folder" noBackground={true} action={() => editor.instance.openDocument()} />
<TextButton label="Open Document" icon="Folder" flush={true} action={() => editor.instance.openDocument()} />
</td>
<td>
<UserInputLabel keysWithLabelsGroups={[[...platformModifiers(false), { key: "KeyO", label: "O" }]]} />
@ -136,7 +135,7 @@
</tr>
<tr>
<td colspan="2">
<TextButton label="Open Demo Artwork" icon="Image" noBackground={true} action={() => editor.instance.demoArtworkDialog()} />
<TextButton label="Open Demo Artwork" icon="Image" flush={true} action={() => editor.instance.demoArtworkDialog()} />
</td>
</tr>
</table>
@ -252,9 +251,9 @@
}
}
.popover-button {
margin: 2px 4px;
}
// .popover-button {
// margin: 2px 4px;
// }
}
.panel-body {
@ -262,6 +261,10 @@
flex: 1 1 100%;
flex-direction: column;
> div {
padding-bottom: 4px;
}
.empty-panel {
background: var(--color-2-mildblack);
margin: 4px;

View File

@ -11,7 +11,6 @@ import {
UpdateToolOptionsLayout,
UpdateToolShelfLayout,
UpdateWorkingColorsLayout,
UpdateGraphViewOverlayButtonLayout,
UpdateNodeGraphBarLayout,
TriggerGraphViewOverlay,
} from "@graphite/wasm-communication/messages";
@ -24,7 +23,6 @@ export function createDocumentState(editor: Editor) {
toolOptionsLayout: defaultWidgetLayout(),
documentBarLayout: defaultWidgetLayout(),
toolShelfLayout: defaultWidgetLayout(),
graphViewOverlayButtonLayout: defaultWidgetLayout(),
workingColorsLayout: defaultWidgetLayout(),
nodeGraphBarLayout: defaultWidgetLayout(),
// Graph view overlay
@ -69,14 +67,6 @@ export function createDocumentState(editor: Editor) {
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateGraphViewOverlayButtonLayout, async (updateGraphViewOverlayButtonLayout) => {
await tick();
update((state) => {
patchWidgetLayout(state.graphViewOverlayButtonLayout, updateGraphViewOverlayButtonLayout);
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateWorkingColorsLayout, async (updateWorkingColorsLayout) => {
await tick();

View File

@ -314,6 +314,7 @@ export const ICON_SVG_STRINGS = Object.fromEntries(Object.entries(ICONS).map(([n
export type IconName = keyof typeof ICONS;
export type IconSize = undefined | 12 | 16 | 24 | 32;
export type PopoverButtonStyle = "DropdownArrow" | "VerticalEllipsis";
// The following helper type declarations allow us to avoid manually maintaining the `IconName` type declaration as a string union paralleling the keys of the
// icon definitions. It lets TypeScript do that for us. Our goal is to define the big key-value pair of icons by constraining its values, but inferring its keys.

View File

@ -3,7 +3,7 @@
import { Transform, Type, plainToClass } from "class-transformer";
import { type IconName, type IconSize } from "@graphite/utility-functions/icons";
import { type PopoverButtonStyle, 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";
@ -918,18 +918,9 @@ export class NumberInput extends WidgetProps {
minWidth!: number;
}
export class OptionalInput extends WidgetProps {
checked!: boolean;
disabled!: boolean;
icon!: IconName;
@Transform(({ value }: { value: string }) => value || undefined)
tooltip!: string | undefined;
}
export class PopoverButton extends WidgetProps {
style!: PopoverButtonStyle | undefined;
icon!: IconName | undefined;
disabled!: boolean;
@ -965,7 +956,7 @@ export class RadioInput extends WidgetProps {
}
export type SeparatorDirection = "Horizontal" | "Vertical";
export type SeparatorType = "Related" | "Unrelated" | "Section" | "List";
export type SeparatorType = "Related" | "Unrelated" | "Section";
export class Separator extends WidgetProps {
direction!: SeparatorDirection;
@ -973,7 +964,7 @@ export class Separator extends WidgetProps {
type!: SeparatorType;
}
export class WorkingColorsButton extends WidgetProps {
export class WorkingColorsInput extends WidgetProps {
@Type(() => Color)
primary!: Color;
@ -1008,7 +999,7 @@ export class TextButton extends WidgetProps {
emphasized!: boolean;
noBackground!: boolean;
flush!: boolean;
minWidth!: number;
@ -1029,7 +1020,7 @@ export type TextButtonWidget = {
label: string;
icon?: IconName;
emphasized?: boolean;
noBackground?: boolean;
flush?: boolean;
minWidth?: number;
disabled?: boolean;
tooltip?: string;
@ -1103,13 +1094,12 @@ const widgetSubTypes = [
{ value: IconLabel, name: "IconLabel" },
{ value: ImageLabel, name: "ImageLabel" },
{ value: NumberInput, name: "NumberInput" },
{ value: OptionalInput, name: "OptionalInput" },
{ value: ParameterExposeButton, name: "ParameterExposeButton" },
{ value: PivotInput, name: "PivotInput" },
{ value: PopoverButton, name: "PopoverButton" },
{ value: RadioInput, name: "RadioInput" },
{ value: Separator, name: "Separator" },
{ value: WorkingColorsButton, name: "WorkingColorsButton" },
{ value: WorkingColorsInput, name: "WorkingColorsInput" },
{ value: TextAreaInput, name: "TextAreaInput" },
{ value: TextButton, name: "TextButton" },
{ value: TextInput, name: "TextInput" },
@ -1293,8 +1283,6 @@ export class UpdateDocumentBarLayout extends WidgetDiffUpdate {}
export class UpdateDocumentModeLayout extends WidgetDiffUpdate {}
export class UpdateGraphViewOverlayButtonLayout extends WidgetDiffUpdate {}
export class UpdateLayersPanelOptionsLayout extends WidgetDiffUpdate {}
// Extends JsMessage instead of WidgetDiffUpdate because the menu bar isn't diffed
@ -1384,7 +1372,6 @@ export const messageMakers: Record<string, MessageMaker> = {
UpdateDocumentRulers,
UpdateDocumentScrollbars,
UpdateEyedropperSamplingState,
UpdateGraphViewOverlayButtonLayout,
UpdateImageData,
UpdateInputHints,
UpdateLayersPanelOptionsLayout,

View File

@ -13,7 +13,7 @@ Before taking the time to read the coming chapters, let's build some context by
You can follow along with this starter project either by watching the tutorial video or referencing the step-by-step breakdown.
***The tutorial isn't ready quite yet, sorry! Please check back very soon. It should be posted by mid-December.***
***The tutorial isn't ready quite yet, sorry! Please check back very soon. It should be posted by late December.***
<!-- TODO -->
<!-- - Video tutorial -->

View File

@ -5,7 +5,7 @@ title = "Features and limitations"
order = 1
+++
Please keep in mind that Graphite is alpha software, meaning it is actively changing and improving. Remember to save you work frequently because crashes are not unheard of.
Please keep in mind that Graphite is alpha software, meaning it is actively changing and improving. Remember to save your work frequently because crashes are not unheard of.
## Current capabilities