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
62 changed files with 326 additions and 521 deletions
@@ -59,6 +59,10 @@ pub enum DocumentMessage {
FlipSelectedLayers {
flip_axis: FlipAxis,
},
GraphViewOverlay {
open: bool,
},
GraphViewOverlayToggle,
GroupSelectedLayers,
ImaginateGenerate,
ImaginateRandom {
@@ -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
}
}
@@ -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);
}
@@ -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.)
@@ -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(),
],
}];