mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-30 03:18:11 +08:00
Add Snapping Options to the Snap Dropdown Menu (#1321)
* [wip]feat: add snapping options
* [wip]fix: use svelte component for optionsWidget
* fix: use apt PopoverButton types
* refactor: minor formatting improvements
* Fix popover layout
* [wip]feat: attempt implementing CheckboxInputData struct
* fix: use correct Checkbox struct 's default method
* fix: revert adding CheckboxInputData struct
- This reverts commit 2a481887fc.
* feat: use checkboxes for snapping options
* feat: add label to dropdown checkbox elements
* fix: separate Snap dropdown menu elements
- move each element into separate row
* [wip]feat: modularize snapping states
- maintain individual snapping states for document
* fix: snapping checkboxes' behavior
- checkboxes now update internal snapping state
* refactor: update snap states individually
- this prevents out-of-sync states
- enables reusing existing snap state object
* feat: snap to boxes and nodes conditionally
* [wip]feat: attempt to invert checkbox on update
- attempt implementing mutable WidgetCallback struct
- attempt using above struct to invert checkbox state on update
* Fix widget diffing
* refactor: remove unused code
* feat: align checkboxes consistently with labels
* feat: use separators to stylize snapping menu
- removes need for custom CSS and label property
- ensures consistency across the application
* refactor: remove unneeded css
---------
Co-authored-by: hypercube <0hypercube@gmail.com>, TrueDoctor <dennis@kobert.dev>
This commit is contained in:
committed by
Keavon Chambers
co-authored by
hypercube <0hypercube@gmail.com>, TrueDoctor
parent
4c9daadb01
commit
743803ce04
@@ -171,7 +171,9 @@ pub enum DocumentMessage {
|
||||
replacement_selected_layers: Vec<Vec<LayerId>>,
|
||||
},
|
||||
SetSnapping {
|
||||
snap: bool,
|
||||
snapping_enabled: Option<bool>,
|
||||
bounding_box_snapping: Option<bool>,
|
||||
node_snapping: Option<bool>,
|
||||
},
|
||||
SetViewMode {
|
||||
view_mode: ViewMode,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::utility_types::error::EditorError;
|
||||
use super::utility_types::misc::DocumentRenderMode;
|
||||
use super::utility_types::misc::{DocumentRenderMode, SnappingOptions, SnappingState};
|
||||
use crate::application::generate_uuid;
|
||||
use crate::consts::{ASYMPTOTIC_EFFECT, DEFAULT_DOCUMENT_NAME, FILE_SAVE_SUFFIX, GRAPHITE_DOCUMENT_VERSION, SCALE_EFFECT, SCROLLBAR_SPACING, VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR};
|
||||
use crate::messages::frontend::utility_types::ExportBounds;
|
||||
@@ -7,6 +7,7 @@ use crate::messages::frontend::utility_types::FileType;
|
||||
use crate::messages::input_mapper::utility_types::macros::action_keys;
|
||||
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
|
||||
use crate::messages::layout::utility_types::misc::LayoutTarget;
|
||||
use crate::messages::layout::utility_types::widget_prelude::{CheckboxInput, TextLabel};
|
||||
use crate::messages::layout::utility_types::widgets::button_widgets::{IconButton, PopoverButton};
|
||||
use crate::messages::layout::utility_types::widgets::input_widgets::{
|
||||
DropdownEntryData, DropdownInput, NumberInput, NumberInputIncrementBehavior, NumberInputMode, OptionalInput, RadioEntryData, RadioInput,
|
||||
@@ -48,7 +49,8 @@ pub struct DocumentMessageHandler {
|
||||
|
||||
pub document_mode: DocumentMode,
|
||||
pub view_mode: ViewMode,
|
||||
pub snapping_enabled: bool,
|
||||
#[serde(skip)]
|
||||
pub snapping_state: SnappingState,
|
||||
pub overlays_visible: bool,
|
||||
|
||||
#[serde(skip)]
|
||||
@@ -83,7 +85,7 @@ impl Default for DocumentMessageHandler {
|
||||
|
||||
document_mode: DocumentMode::DesignMode,
|
||||
view_mode: ViewMode::default(),
|
||||
snapping_enabled: true,
|
||||
snapping_state: SnappingState::default(),
|
||||
overlays_visible: true,
|
||||
|
||||
document_undo_history: VecDeque::new(),
|
||||
@@ -845,8 +847,20 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
||||
let additional_layers = replacement_selected_layers;
|
||||
responses.add_front(AddSelectedLayers { additional_layers });
|
||||
}
|
||||
SetSnapping { snap } => {
|
||||
self.snapping_enabled = snap;
|
||||
SetSnapping {
|
||||
snapping_enabled,
|
||||
bounding_box_snapping,
|
||||
node_snapping,
|
||||
} => {
|
||||
if let Some(state) = snapping_enabled {
|
||||
self.snapping_state.snapping_enabled = state
|
||||
};
|
||||
if let Some(state) = bounding_box_snapping {
|
||||
self.snapping_state.bounding_box_snapping = state
|
||||
}
|
||||
if let Some(state) = node_snapping {
|
||||
self.snapping_state.node_snapping = state
|
||||
};
|
||||
}
|
||||
SetViewMode { view_mode } => {
|
||||
self.view_mode = view_mode;
|
||||
@@ -1553,17 +1567,88 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
|
||||
pub fn update_document_widgets(&self, responses: &mut VecDeque<Message>) {
|
||||
let snapping_state = self.snapping_state.clone();
|
||||
let mut widgets = vec![
|
||||
WidgetHolder::new(Widget::OptionalInput(OptionalInput {
|
||||
checked: self.snapping_enabled,
|
||||
checked: snapping_state.snapping_enabled,
|
||||
icon: "Snapping".into(),
|
||||
tooltip: "Snapping".into(),
|
||||
on_update: WidgetCallback::new(|optional_input: &OptionalInput| DocumentMessage::SetSnapping { snap: optional_input.checked }.into()),
|
||||
on_update: WidgetCallback::new(move |optional_input: &OptionalInput| {
|
||||
let snapping_enabled = optional_input.checked;
|
||||
DocumentMessage::SetSnapping {
|
||||
snapping_enabled: Some(snapping_enabled),
|
||||
bounding_box_snapping: Some(snapping_state.bounding_box_snapping),
|
||||
node_snapping: Some(snapping_state.node_snapping),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
|
||||
header: "Snapping".into(),
|
||||
text: "Coming soon".into(),
|
||||
text: "Select the vectors to snap to.".into(), // TODO: check whether this is an apt description
|
||||
options_widget: vec![
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
WidgetHolder::new(Widget::CheckboxInput(CheckboxInput {
|
||||
tooltip: SnappingOptions::BoundingBoxes.to_string(),
|
||||
checked: snapping_state.bounding_box_snapping,
|
||||
on_update: WidgetCallback::new(move |input: &CheckboxInput| {
|
||||
DocumentMessage::SetSnapping {
|
||||
snapping_enabled: None,
|
||||
bounding_box_snapping: Some(input.checked),
|
||||
node_snapping: None,
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
})),
|
||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: SnappingOptions::BoundingBoxes.to_string(),
|
||||
table_align: false,
|
||||
min_width: 60,
|
||||
..Default::default()
|
||||
})),
|
||||
// adds appropriate space between row elements
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
direction: SeparatorDirection::Vertical,
|
||||
separator_type: SeparatorType::Related,
|
||||
})),
|
||||
],
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
WidgetHolder::new(Widget::CheckboxInput(CheckboxInput {
|
||||
checked: self.snapping_state.node_snapping,
|
||||
tooltip: SnappingOptions::Points.to_string(),
|
||||
on_update: WidgetCallback::new(|input: &CheckboxInput| {
|
||||
DocumentMessage::SetSnapping {
|
||||
snapping_enabled: None,
|
||||
bounding_box_snapping: None,
|
||||
node_snapping: Some(input.checked),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
})),
|
||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: SnappingOptions::Points.to_string(),
|
||||
table_align: false,
|
||||
min_width: 60,
|
||||
..Default::default()
|
||||
})),
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
|
||||
+1
-1
@@ -1866,7 +1866,7 @@ pub fn collect_node_types() -> Vec<FrontendNodeType> {
|
||||
impl DocumentNodeType {
|
||||
/// Generate a [`DocumentNodeImplementation`] from this node type, using a nested network.
|
||||
pub fn generate_implementation(&self) -> DocumentNodeImplementation {
|
||||
let num_inputs = self.inputs.len();
|
||||
// let num_inputs = self.inputs.len();
|
||||
|
||||
let inner_network = match &self.identifier {
|
||||
NodeImplementation::DocumentNode(network) => network.clone(),
|
||||
|
||||
@@ -74,3 +74,36 @@ pub enum DocumentRenderMode<'a> {
|
||||
OnlyBelowLayerInFolder(&'a [LayerId]),
|
||||
LayerCutout(&'a [LayerId], Color),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
/// SnappingState determines the current individual snapping states
|
||||
pub struct SnappingState {
|
||||
pub snapping_enabled: bool,
|
||||
pub bounding_box_snapping: bool,
|
||||
pub node_snapping: bool,
|
||||
}
|
||||
|
||||
impl Default for SnappingState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
snapping_enabled: true,
|
||||
bounding_box_snapping: true,
|
||||
node_snapping: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: implement icons for SnappingOptions eventually
|
||||
pub enum SnappingOptions {
|
||||
BoundingBoxes,
|
||||
Points,
|
||||
}
|
||||
|
||||
impl fmt::Display for SnappingOptions {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
SnappingOptions::BoundingBoxes => write!(f, "Bounding Boxes"),
|
||||
SnappingOptions::Points => write!(f, "Points"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user