mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-22 14:18:11 +08:00
Move selected node list from DocumentMetadata to the document (#1565)
This commit is contained in:
@@ -45,7 +45,7 @@ impl Pivot {
|
||||
|
||||
/// Recomputes the pivot position and transform.
|
||||
fn recalculate_pivot(&mut self, document: &DocumentMessageHandler) {
|
||||
let mut layers = document.selected_visible_layers();
|
||||
let mut layers = document.selected_nodes.selected_visible_layers(document.network(), document.metadata());
|
||||
let Some(first) = layers.next() else {
|
||||
// If no layers are selected then we revert things back to default
|
||||
self.normalized_pivot = DVec2::splat(0.5);
|
||||
@@ -65,8 +65,9 @@ impl Pivot {
|
||||
} else {
|
||||
// If more than one layer is selected we use the AABB with the mean of the pivots
|
||||
let xy_summation = document
|
||||
.selected_visible_layers()
|
||||
.map(|layer| graph_modification_utils::get_viewport_pivot(layer, &document.network, &document.document_metadata))
|
||||
.selected_nodes
|
||||
.selected_visible_layers(document.network(), document.metadata())
|
||||
.map(|layer| graph_modification_utils::get_viewport_pivot(layer, &document.network, &document.metadata))
|
||||
.reduce(|a, b| a + b)
|
||||
.unwrap_or_default();
|
||||
|
||||
@@ -100,7 +101,7 @@ impl Pivot {
|
||||
|
||||
/// Sets the viewport position of the pivot for all selected layers.
|
||||
pub fn set_viewport_position(&self, position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
for layer in document.selected_visible_layers() {
|
||||
for layer in document.selected_nodes.selected_visible_layers(document.network(), document.metadata()) {
|
||||
let transform = Self::get_layer_pivot_transform(layer, document);
|
||||
let pivot = transform.inverse().transform_point2(position);
|
||||
// Only update the pivot when computed position is finite. Infinite can happen when scale is 0.
|
||||
|
||||
@@ -266,10 +266,10 @@ impl BrushToolData {
|
||||
fn load_existing_strokes(&mut self, document: &DocumentMessageHandler) -> Option<LayerNodeIdentifier> {
|
||||
self.transform = DAffine2::IDENTITY;
|
||||
|
||||
if document.metadata().selected_layers().count() != 1 {
|
||||
if document.selected_nodes.selected_layers(document.metadata()).count() != 1 {
|
||||
return None;
|
||||
}
|
||||
let Some(layer) = document.metadata().selected_layers().next() else {
|
||||
let Some(layer) = document.selected_nodes.selected_layers(document.metadata()).next() else {
|
||||
return None;
|
||||
};
|
||||
|
||||
|
||||
@@ -295,7 +295,7 @@ impl Fsm for GradientToolFsmState {
|
||||
(_, GradientToolMessage::Overlays(mut overlay_context)) => {
|
||||
let selected = tool_data.selected_gradient.as_ref();
|
||||
|
||||
for layer in document.selected_visible_layers() {
|
||||
for layer in document.selected_nodes.selected_visible_layers(document.network(), document.metadata()) {
|
||||
let Some(gradient) = get_gradient(layer, &document.network) else { continue };
|
||||
let transform = gradient_space_transform(layer, document);
|
||||
let dragging = selected.filter(|selected| selected.layer == layer).map(|selected| selected.dragging);
|
||||
@@ -366,7 +366,7 @@ impl Fsm for GradientToolFsmState {
|
||||
self
|
||||
}
|
||||
(_, GradientToolMessage::InsertStop) => {
|
||||
for layer in document.selected_visible_layers() {
|
||||
for layer in document.selected_nodes.selected_visible_layers(document.network(), document.metadata()) {
|
||||
let Some(mut gradient) = get_gradient(layer, &document.network) else { continue };
|
||||
let transform = gradient_space_transform(layer, document);
|
||||
|
||||
@@ -407,7 +407,7 @@ impl Fsm for GradientToolFsmState {
|
||||
let tolerance = (MANIPULATOR_GROUP_MARKER_SIZE * 2.).powi(2);
|
||||
|
||||
let mut dragging = false;
|
||||
for layer in document.selected_visible_layers() {
|
||||
for layer in document.selected_nodes.selected_visible_layers(document.network(), document.metadata()) {
|
||||
let Some(gradient) = get_gradient(layer, &document.network) else { continue };
|
||||
let transform = gradient_space_transform(layer, document);
|
||||
|
||||
@@ -448,7 +448,7 @@ impl Fsm for GradientToolFsmState {
|
||||
|
||||
// Apply the gradient to the selected layer
|
||||
if let Some(layer) = selected_layer {
|
||||
if !document.metadata().selected_layers_contains(layer) {
|
||||
if !document.selected_nodes.selected_layers_contains(layer, document.metadata()) {
|
||||
let nodes = vec![layer.to_node()];
|
||||
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes });
|
||||
|
||||
@@ -223,7 +223,7 @@ impl PathToolData {
|
||||
let _selected_layers = shape_editor.selected_layers().cloned().collect::<Vec<_>>();
|
||||
|
||||
// Select the first point within the threshold (in pixels)
|
||||
if let Some(selected_points) = shape_editor.select_point(&document.network, &document.document_metadata, input.mouse.position, SELECTION_THRESHOLD, shift) {
|
||||
if let Some(selected_points) = shape_editor.select_point(&document.network, &document.metadata, input.mouse.position, SELECTION_THRESHOLD, shift) {
|
||||
self.start_dragging_point(selected_points, input, document, responses);
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
|
||||
@@ -298,7 +298,7 @@ impl PathToolData {
|
||||
|
||||
// Move the selected points with the mouse
|
||||
let snapped_position = self.snap_manager.snap_position(responses, document, input.mouse.position);
|
||||
shape_editor.move_selected_points(&document.network, &document.document_metadata, snapped_position - self.previous_mouse_position, shift, responses);
|
||||
shape_editor.move_selected_points(&document.network, &document.metadata, snapped_position - self.previous_mouse_position, shift, responses);
|
||||
self.previous_mouse_position = snapped_position;
|
||||
}
|
||||
}
|
||||
@@ -316,7 +316,7 @@ impl Fsm for PathToolFsmState {
|
||||
match (self, event) {
|
||||
(_, PathToolMessage::SelectionChanged) => {
|
||||
// Set the newly targeted layers to visible
|
||||
let target_layers = document.metadata().selected_layers().collect();
|
||||
let target_layers = document.selected_nodes.selected_layers(document.metadata()).collect();
|
||||
shape_editor.set_selected_layers(target_layers);
|
||||
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
@@ -362,12 +362,7 @@ impl Fsm for PathToolFsmState {
|
||||
if tool_data.drag_start_pos == tool_data.previous_mouse_position {
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] });
|
||||
} else {
|
||||
shape_editor.select_all_in_quad(
|
||||
&document.network,
|
||||
&document.document_metadata,
|
||||
[tool_data.drag_start_pos, tool_data.previous_mouse_position],
|
||||
!shift_pressed,
|
||||
);
|
||||
shape_editor.select_all_in_quad(&document.network, &document.metadata, [tool_data.drag_start_pos, tool_data.previous_mouse_position], !shift_pressed);
|
||||
}
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
|
||||
@@ -381,12 +376,7 @@ impl Fsm for PathToolFsmState {
|
||||
if tool_data.drag_start_pos == tool_data.previous_mouse_position {
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] });
|
||||
} else {
|
||||
shape_editor.select_all_in_quad(
|
||||
&document.network,
|
||||
&document.document_metadata,
|
||||
[tool_data.drag_start_pos, tool_data.previous_mouse_position],
|
||||
!shift_pressed,
|
||||
);
|
||||
shape_editor.select_all_in_quad(&document.network, &document.metadata, [tool_data.drag_start_pos, tool_data.previous_mouse_position], !shift_pressed);
|
||||
}
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
responses.add(PathToolMessage::SelectedPointUpdated);
|
||||
@@ -398,16 +388,16 @@ impl Fsm for PathToolFsmState {
|
||||
let shift_pressed = input.keyboard.get(shift_mirror_distance as usize);
|
||||
|
||||
let nearest_point = shape_editor
|
||||
.find_nearest_point_indices(&document.network, &document.document_metadata, input.mouse.position, SELECTION_THRESHOLD)
|
||||
.find_nearest_point_indices(&document.network, &document.metadata, input.mouse.position, SELECTION_THRESHOLD)
|
||||
.map(|(_, nearest_point)| nearest_point);
|
||||
|
||||
shape_editor.delete_selected_handles_with_zero_length(&document.network, &document.document_metadata, &tool_data.opposing_handle_lengths, responses);
|
||||
shape_editor.delete_selected_handles_with_zero_length(&document.network, &document.metadata, &tool_data.opposing_handle_lengths, responses);
|
||||
|
||||
if tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD && !shift_pressed {
|
||||
let clicked_selected = shape_editor.selected_points().any(|&point| nearest_point == Some(point));
|
||||
if clicked_selected {
|
||||
shape_editor.deselect_all();
|
||||
shape_editor.select_point(&document.network, &document.document_metadata, input.mouse.position, SELECTION_THRESHOLD, false);
|
||||
shape_editor.select_point(&document.network, &document.metadata, input.mouse.position, SELECTION_THRESHOLD, false);
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
}
|
||||
}
|
||||
@@ -428,9 +418,9 @@ impl Fsm for PathToolFsmState {
|
||||
}
|
||||
(_, PathToolMessage::InsertPoint) => {
|
||||
// First we try and flip the sharpness (if they have clicked on an anchor)
|
||||
if !shape_editor.flip_sharp(&document.network, &document.document_metadata, input.mouse.position, SELECTION_TOLERANCE, responses) {
|
||||
if !shape_editor.flip_sharp(&document.network, &document.metadata, input.mouse.position, SELECTION_TOLERANCE, responses) {
|
||||
// If not, then we try and split the path that may have been clicked upon
|
||||
shape_editor.split(&document.network, &document.document_metadata, input.mouse.position, SELECTION_TOLERANCE, responses);
|
||||
shape_editor.split(&document.network, &document.metadata, input.mouse.position, SELECTION_TOLERANCE, responses);
|
||||
}
|
||||
|
||||
responses.add(PathToolMessage::SelectedPointUpdated);
|
||||
@@ -443,7 +433,7 @@ impl Fsm for PathToolFsmState {
|
||||
}
|
||||
(_, PathToolMessage::PointerMove { .. }) => self,
|
||||
(_, PathToolMessage::NudgeSelectedPoints { delta_x, delta_y }) => {
|
||||
shape_editor.move_selected_points(&document.network, &document.document_metadata, (delta_x, delta_y).into(), true, responses);
|
||||
shape_editor.move_selected_points(&document.network, &document.metadata, (delta_x, delta_y).into(), true, responses);
|
||||
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
@@ -454,18 +444,18 @@ impl Fsm for PathToolFsmState {
|
||||
}
|
||||
(_, PathToolMessage::SelectedPointXChanged { new_x }) => {
|
||||
if let Some(&SingleSelectedPoint { coordinates, id, layer, .. }) = tool_data.selection_status.as_one() {
|
||||
shape_editor.reposition_control_point(&id, responses, &document.network, &document.document_metadata, DVec2::new(new_x, coordinates.y), layer);
|
||||
shape_editor.reposition_control_point(&id, responses, &document.network, &document.metadata, DVec2::new(new_x, coordinates.y), layer);
|
||||
}
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
(_, PathToolMessage::SelectedPointYChanged { new_y }) => {
|
||||
if let Some(&SingleSelectedPoint { coordinates, id, layer, .. }) = tool_data.selection_status.as_one() {
|
||||
shape_editor.reposition_control_point(&id, responses, &document.network, &document.document_metadata, DVec2::new(coordinates.x, new_y), layer);
|
||||
shape_editor.reposition_control_point(&id, responses, &document.network, &document.metadata, DVec2::new(coordinates.x, new_y), layer);
|
||||
}
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
(_, PathToolMessage::SelectedPointUpdated) => {
|
||||
tool_data.selection_status = get_selection_status(&document.network, &document.document_metadata, shape_editor);
|
||||
tool_data.selection_status = get_selection_status(&document.network, &document.metadata, shape_editor);
|
||||
self
|
||||
}
|
||||
(_, PathToolMessage::ManipulatorAngleMakeSmooth) => {
|
||||
|
||||
@@ -720,7 +720,7 @@ fn should_extend(document: &DocumentMessageHandler, pos: DVec2, tolerance: f64)
|
||||
let mut best = None;
|
||||
let mut best_distance_squared = tolerance * tolerance;
|
||||
|
||||
for layer in document.metadata().selected_layers() {
|
||||
for layer in document.selected_nodes.selected_layers(document.metadata()) {
|
||||
let viewspace = document.metadata().transform_to_viewport(layer);
|
||||
|
||||
let subpaths = get_subpaths(layer, &document.network)?;
|
||||
|
||||
@@ -381,27 +381,32 @@ impl Fsm for SelectToolFsmState {
|
||||
};
|
||||
match (self, event) {
|
||||
(_, SelectToolMessage::Overlays(mut overlay_context)) => {
|
||||
let selected_layers_count = document.metadata().selected_layers().count();
|
||||
let selected_layers_count = document.selected_nodes.selected_layers(document.metadata()).count();
|
||||
tool_data.selected_layers_changed = selected_layers_count != tool_data.selected_layers_count;
|
||||
tool_data.selected_layers_count = selected_layers_count;
|
||||
|
||||
// Outline selected layers
|
||||
for layer in document.selected_visible_layers() {
|
||||
for layer in document.selected_nodes.selected_visible_layers(document.network(), document.metadata()) {
|
||||
overlay_context.outline(document.metadata().layer_outline(layer), document.metadata().transform_to_viewport(layer));
|
||||
}
|
||||
|
||||
// Get the layer the user is hovering over
|
||||
let click = document.click(input.mouse.position, &document.network);
|
||||
let not_selected_click = click.filter(|&hovered_layer| !document.metadata().selected_layers_contains(hovered_layer));
|
||||
let not_selected_click = click.filter(|&hovered_layer| !document.selected_nodes.selected_layers_contains(hovered_layer, document.metadata()));
|
||||
if let Some(layer) = not_selected_click {
|
||||
overlay_context.outline(document.metadata().layer_outline(layer), document.metadata().transform_to_viewport(layer));
|
||||
}
|
||||
|
||||
// Update bounds
|
||||
let transform = document.selected_visible_layers().next().map(|layer| document.metadata().transform_to_viewport(layer));
|
||||
let transform = document
|
||||
.selected_nodes
|
||||
.selected_visible_layers(document.network(), document.metadata())
|
||||
.next()
|
||||
.map(|layer| document.metadata().transform_to_viewport(layer));
|
||||
let transform = transform.unwrap_or(DAffine2::IDENTITY);
|
||||
let bounds = document
|
||||
.selected_visible_layers()
|
||||
.selected_nodes
|
||||
.selected_visible_layers(document.network(), document.metadata())
|
||||
.filter_map(|layer| {
|
||||
document
|
||||
.metadata()
|
||||
@@ -462,7 +467,7 @@ impl Fsm for SelectToolFsmState {
|
||||
.map(|bounding_box| bounding_box.check_rotate(input.mouse.position))
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut selected: Vec<_> = document.selected_visible_layers().collect();
|
||||
let mut selected: Vec<_> = document.selected_nodes.selected_visible_layers(document.network(), document.metadata()).collect();
|
||||
let intersection = document.click(input.mouse.position, &document.network);
|
||||
|
||||
// If the user is dragging the bounding box bounds, go into ResizingBounds mode.
|
||||
@@ -491,7 +496,7 @@ impl Fsm for SelectToolFsmState {
|
||||
&tool_data.layers_dragging,
|
||||
responses,
|
||||
&document.network,
|
||||
&document.document_metadata,
|
||||
&document.metadata,
|
||||
None,
|
||||
&ToolType::Select,
|
||||
);
|
||||
@@ -510,7 +515,7 @@ impl Fsm for SelectToolFsmState {
|
||||
&selected,
|
||||
responses,
|
||||
&document.network,
|
||||
&document.document_metadata,
|
||||
&document.metadata,
|
||||
None,
|
||||
&ToolType::Select,
|
||||
);
|
||||
@@ -630,7 +635,7 @@ impl Fsm for SelectToolFsmState {
|
||||
selected,
|
||||
responses,
|
||||
&document.network,
|
||||
&document.document_metadata,
|
||||
&document.metadata,
|
||||
None,
|
||||
&ToolType::Select,
|
||||
);
|
||||
@@ -665,7 +670,7 @@ impl Fsm for SelectToolFsmState {
|
||||
&tool_data.layers_dragging,
|
||||
responses,
|
||||
&document.network,
|
||||
&document.document_metadata,
|
||||
&document.metadata,
|
||||
None,
|
||||
&ToolType::Select,
|
||||
);
|
||||
@@ -723,7 +728,11 @@ impl Fsm for SelectToolFsmState {
|
||||
let intersection = document.intersect_quad(quad, &document.network);
|
||||
|
||||
if let Some(path) = intersection.last() {
|
||||
let replacement_selected_layers: Vec<_> = document.metadata().selected_layers().filter(|&layer| !path.starts_with(layer, document.metadata())).collect();
|
||||
let replacement_selected_layers: Vec<_> = document
|
||||
.selected_nodes
|
||||
.selected_layers(document.metadata())
|
||||
.filter(|&layer| !path.starts_with(layer, document.metadata()))
|
||||
.collect();
|
||||
|
||||
tool_data.layers_dragging.clear();
|
||||
tool_data.layers_dragging.extend(replacement_selected_layers.iter());
|
||||
@@ -791,7 +800,7 @@ impl Fsm for SelectToolFsmState {
|
||||
(SelectToolFsmState::DrawingBox, SelectToolMessage::DragStop { .. } | SelectToolMessage::Enter) => {
|
||||
let quad = tool_data.selection_quad();
|
||||
let new_selected: HashSet<_> = document.intersect_quad(quad, &document.network).collect();
|
||||
let current_selected: HashSet<_> = document.metadata().selected_layers().collect();
|
||||
let current_selected: HashSet<_> = document.selected_nodes.selected_layers(document.metadata()).collect();
|
||||
if new_selected != current_selected {
|
||||
tool_data.layers_dragging = new_selected.into_iter().collect();
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
@@ -804,7 +813,7 @@ impl Fsm for SelectToolFsmState {
|
||||
SelectToolFsmState::Ready
|
||||
}
|
||||
(SelectToolFsmState::Ready, SelectToolMessage::Enter) => {
|
||||
let mut selected_layers = document.metadata().selected_layers();
|
||||
let mut selected_layers = document.selected_nodes.selected_layers(document.metadata());
|
||||
|
||||
if let Some(layer) = selected_layers.next() {
|
||||
// Check that only one layer is selected
|
||||
@@ -832,7 +841,7 @@ impl Fsm for SelectToolFsmState {
|
||||
&tool_data.layers_dragging,
|
||||
responses,
|
||||
&document.network,
|
||||
&document.document_metadata,
|
||||
&document.metadata,
|
||||
None,
|
||||
&ToolType::Select,
|
||||
);
|
||||
@@ -908,7 +917,9 @@ impl Fsm for SelectToolFsmState {
|
||||
|
||||
fn drag_shallowest_manipulation(responses: &mut VecDeque<Message>, selected: Vec<LayerNodeIdentifier>, tool_data: &mut SelectToolData, document: &DocumentMessageHandler) {
|
||||
let layer = selected[0];
|
||||
let ancestor = layer.ancestors(document.metadata()).find(|&ancestor| document.metadata().selected_layers_contains(ancestor));
|
||||
let ancestor = layer
|
||||
.ancestors(document.metadata())
|
||||
.find(|&ancestor| document.selected_nodes.selected_layers_contains(ancestor, document.metadata()));
|
||||
|
||||
let new_selected = ancestor.unwrap_or_else(|| layer.child_of_root(document.metadata()));
|
||||
|
||||
@@ -932,15 +943,16 @@ fn drag_deepest_manipulation(responses: &mut VecDeque<Message>, mut selected: Ve
|
||||
}
|
||||
|
||||
fn edit_layer_shallowest_manipulation(document: &DocumentMessageHandler, layer: LayerNodeIdentifier, responses: &mut VecDeque<Message>) {
|
||||
if document.metadata().selected_layers_contains(layer) {
|
||||
if document.selected_nodes.selected_layers_contains(layer, document.metadata()) {
|
||||
responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Path });
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(new_selected) = layer
|
||||
.ancestors(document.metadata())
|
||||
.find(|ancestor| ancestor.parent(document.metadata()).is_some_and(|parent| document.metadata().selected_layers_contains(parent)))
|
||||
else {
|
||||
let Some(new_selected) = layer.ancestors(document.metadata()).find(|ancestor| {
|
||||
ancestor
|
||||
.parent(document.metadata())
|
||||
.is_some_and(|parent| document.selected_nodes.selected_layers_contains(parent, document.metadata()))
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -344,9 +344,9 @@ impl TextToolData {
|
||||
}
|
||||
|
||||
fn can_edit_selected(document: &DocumentMessageHandler) -> Option<LayerNodeIdentifier> {
|
||||
let mut selected_layers = document.metadata().selected_layers();
|
||||
|
||||
let mut selected_layers = document.selected_nodes.selected_layers(document.metadata());
|
||||
let layer = selected_layers.next()?;
|
||||
|
||||
// Check that only one layer is selected
|
||||
if selected_layers.next().is_some() {
|
||||
return None;
|
||||
@@ -392,7 +392,7 @@ impl Fsm for TextToolFsmState {
|
||||
TextToolFsmState::Editing
|
||||
}
|
||||
(_, TextToolMessage::Overlays(mut overlay_context)) => {
|
||||
for layer in document.metadata().selected_layers() {
|
||||
for layer in document.selected_nodes.selected_layers(document.metadata()) {
|
||||
let Some((text, font, font_size)) = graph_modification_utils::get_text(layer, &document.network) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
@@ -48,7 +48,7 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
|
||||
|
||||
let using_path_tool = tool_data.active_tool_type == ToolType::Path;
|
||||
|
||||
let selected_layers = document.metadata().selected_layers().collect::<Vec<_>>();
|
||||
let selected_layers = document.selected_nodes.selected_layers(document.metadata()).collect::<Vec<_>>();
|
||||
|
||||
let mut selected = Selected::new(
|
||||
&mut self.original_transforms,
|
||||
@@ -56,7 +56,7 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
|
||||
&selected_layers,
|
||||
responses,
|
||||
&document.network,
|
||||
&document.document_metadata,
|
||||
&document.metadata,
|
||||
Some(shape_editor),
|
||||
&tool_data.active_tool_type,
|
||||
);
|
||||
@@ -222,7 +222,7 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
|
||||
self.mouse_position = input.mouse.position;
|
||||
}
|
||||
SelectionChanged => {
|
||||
let target_layers = document.metadata().selected_layers().collect();
|
||||
let target_layers = document.selected_nodes.selected_layers(document.metadata()).collect();
|
||||
shape_editor.set_selected_layers(target_layers);
|
||||
}
|
||||
TypeBackspace => self.transform_operation.grs_typed(self.typing.type_backspace(), &mut selected, self.snap),
|
||||
|
||||
Reference in New Issue
Block a user