mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-07-07 15:55:00 +00:00
add: add all of the stuff for path tool
This commit is contained in:
parent
06e8e4e6e0
commit
7d46c7dcfd
4 changed files with 182 additions and 125 deletions
|
@ -9,7 +9,7 @@ use crate::messages::tool::tool_messages::path_tool::PathOptionsUpdate;
|
|||
use crate::messages::tool::tool_messages::select_tool::SelectOptionsUpdate;
|
||||
use crate::messages::tool::tool_messages::tool_prelude::*;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_std::transform::ReferencePoint;
|
||||
use graphene_std::{transform::ReferencePoint, vector::ManipulatorPointId};
|
||||
use std::fmt;
|
||||
|
||||
pub fn pin_pivot_widget(disabled: bool, source: Source) -> WidgetHolder {
|
||||
|
@ -79,6 +79,7 @@ pub struct Dot {
|
|||
pub pivot: Pivot,
|
||||
pub state: DotState,
|
||||
pub layer: Option<LayerNodeIdentifier>,
|
||||
pub point: Option<ManipulatorPointId>,
|
||||
}
|
||||
|
||||
impl Dot {
|
||||
|
@ -223,6 +224,23 @@ impl Pivot {
|
|||
self.pivot = Some(self.transform_from_normalized.transform_point2(self.normalized_pivot));
|
||||
}
|
||||
|
||||
pub fn recalculate_pivot_for_layer(&mut self, document: &DocumentMessageHandler, layer: LayerNodeIdentifier, bounds: Option<[DVec2; 2]>) {
|
||||
if !self.active {
|
||||
return;
|
||||
}
|
||||
|
||||
let selected = document.network_interface.selected_nodes();
|
||||
if !selected.has_selected_nodes() {
|
||||
self.normalized_pivot = DVec2::splat(0.5);
|
||||
self.pivot = None;
|
||||
return;
|
||||
};
|
||||
|
||||
let [min, max] = bounds.unwrap_or([DVec2::ZERO, DVec2::ONE]);
|
||||
self.transform_from_normalized = DAffine2::from_translation(min) * DAffine2::from_scale(max - min);
|
||||
self.pivot = Some(self.transform_from_normalized.transform_point2(self.normalized_pivot));
|
||||
}
|
||||
|
||||
pub fn update(&mut self, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, draw_data: Option<(f64,)>, draw: bool) {
|
||||
if !overlay_context.visibility_settings.pivot() {
|
||||
self.active = false;
|
||||
|
|
|
@ -500,6 +500,7 @@ struct PathToolData {
|
|||
dragging_state: DraggingState,
|
||||
angle: f64,
|
||||
dot: Dot,
|
||||
ordered_points: Vec<ManipulatorPointId>,
|
||||
opposite_handle_position: Option<DVec2>,
|
||||
last_clicked_point_was_selected: bool,
|
||||
last_clicked_segment_was_selected: bool,
|
||||
|
@ -1377,6 +1378,12 @@ impl PathToolData {
|
|||
fn get_as_dot(&self) -> Dot {
|
||||
self.dot.clone()
|
||||
}
|
||||
|
||||
fn sync_history(&mut self, points: &Vec<ManipulatorPointId>) {
|
||||
self.ordered_points.retain(|layer| points.contains(layer));
|
||||
self.ordered_points.extend(points.iter().find(|&layer| !self.ordered_points.contains(layer)));
|
||||
self.dot.point = self.ordered_points.last().map(|x| *x)
|
||||
}
|
||||
}
|
||||
|
||||
impl Fsm for PathToolFsmState {
|
||||
|
@ -1389,9 +1396,6 @@ impl Fsm for PathToolFsmState {
|
|||
update_dynamic_hints(self, responses, shape_editor, document, tool_data, tool_options);
|
||||
|
||||
let ToolMessage::Path(event) = event else { return self };
|
||||
if !matches!(event, PathToolMessage::Overlays(_) | PathToolMessage::UpdateSelectedPointsStatus { .. }) {
|
||||
// debug!("{event:?}");
|
||||
}
|
||||
match (self, event) {
|
||||
(_, PathToolMessage::SelectionChanged) => {
|
||||
// Set the newly targeted layers to visible
|
||||
|
@ -1408,6 +1412,9 @@ impl Fsm for PathToolFsmState {
|
|||
shape_editor.update_selected_anchors_status(display_anchors);
|
||||
shape_editor.update_selected_handles_status(display_handles);
|
||||
|
||||
let new_points = shape_editor.selected_points().copied().collect::<Vec<_>>();
|
||||
tool_data.sync_history(&new_points);
|
||||
|
||||
self
|
||||
}
|
||||
(_, PathToolMessage::Overlays(mut overlay_context)) => {
|
||||
|
|
|
@ -361,7 +361,7 @@ struct SelectToolData {
|
|||
lasso_polygon: Vec<ViewportPosition>,
|
||||
selection_mode: Option<SelectionMode>,
|
||||
layers_dragging: Vec<LayerNodeIdentifier>, // Unordered, often used as temporary buffer
|
||||
orderer_layers: Vec<LayerNodeIdentifier>, // Ordered list of layers
|
||||
ordered_layers: Vec<LayerNodeIdentifier>, // Ordered list of layers
|
||||
layer_selected_on_start: Option<LayerNodeIdentifier>,
|
||||
select_single_layer: Option<LayerNodeIdentifier>,
|
||||
axis_align: bool,
|
||||
|
@ -548,9 +548,9 @@ impl SelectToolData {
|
|||
}
|
||||
|
||||
fn sync_history(&mut self) {
|
||||
self.orderer_layers.retain(|layer| self.layers_dragging.contains(layer));
|
||||
self.orderer_layers.extend(self.layers_dragging.iter().find(|&layer| !self.orderer_layers.contains(layer)));
|
||||
self.dot.layer = self.orderer_layers.last().map(|x| *x)
|
||||
self.ordered_layers.retain(|layer| self.layers_dragging.contains(layer));
|
||||
self.ordered_layers.extend(self.layers_dragging.iter().find(|&layer| !self.ordered_layers.contains(layer)));
|
||||
self.dot.layer = self.ordered_layers.last().map(|x| *x)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -38,6 +38,8 @@ pub struct TransformLayerMessageHandler {
|
|||
dot: Dot,
|
||||
pivot: ViewportPosition,
|
||||
|
||||
path_bounds: Option<[DVec2; 2]>,
|
||||
|
||||
local_pivot: DocumentPosition,
|
||||
local_mouse_start: DocumentPosition,
|
||||
grab_target: DocumentPosition,
|
||||
|
@ -64,43 +66,66 @@ impl TransformLayerMessageHandler {
|
|||
}
|
||||
|
||||
fn calculate_pivot(
|
||||
document: &DocumentMessageHandler,
|
||||
selected_points: &Vec<&ManipulatorPointId>,
|
||||
vector_data: &VectorData,
|
||||
viewspace: DAffine2,
|
||||
get_location: impl Fn(&ManipulatorPointId) -> Option<DVec2>,
|
||||
dot: &Dot,
|
||||
) -> Option<(DVec2, DVec2)> {
|
||||
dot: &mut Dot,
|
||||
layers: LayerNodeIdentifier,
|
||||
) -> (Option<(DVec2, DVec2)>, Option<[DVec2; 2]>) {
|
||||
let average_position = || {
|
||||
let mut point_count = 0;
|
||||
selected_points.iter().filter_map(|p| get_location(p)).inspect(|_| point_count += 1).sum::<DVec2>() / point_count as f64
|
||||
};
|
||||
let bounds = selected_points.iter().filter_map(|p| get_location(p)).fold(None, |acc: Option<[DVec2; 2]>, point| {
|
||||
if let Some([mut min, mut max]) = acc {
|
||||
min.x = min.x.min(point.x);
|
||||
min.y = min.y.min(point.y);
|
||||
max.x = max.x.max(point.x);
|
||||
max.y = max.y.max(point.y);
|
||||
Some([min, max])
|
||||
} else {
|
||||
Some([point, point])
|
||||
}
|
||||
});
|
||||
dot.pivot.recalculate_pivot_for_layer(document, layers, bounds);
|
||||
let position = || {
|
||||
if !dot.state.enabled {
|
||||
return average_position();
|
||||
}
|
||||
match dot.state.dot {
|
||||
DotType::Average => average_position(),
|
||||
DotType::Active => selected_points.first().map(|p| get_location(p)).flatten().unwrap_or_else(average_position),
|
||||
DotType::Pivot => average_position(),
|
||||
{
|
||||
if dot.state.enabled {
|
||||
match dot.state.dot {
|
||||
DotType::Average => None,
|
||||
DotType::Active => dot.point.and_then(|p| get_location(&p)),
|
||||
DotType::Pivot => dot.pivot.position(),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
.unwrap_or_else(average_position)
|
||||
};
|
||||
let [point] = selected_points.as_slice() else {
|
||||
// Handle the case where there are multiple points
|
||||
let position = position();
|
||||
return Some((position, position));
|
||||
return (Some((position, position)), bounds);
|
||||
};
|
||||
|
||||
match point {
|
||||
ManipulatorPointId::PrimaryHandle(_) | ManipulatorPointId::EndHandle(_) => {
|
||||
// Get the anchor position and transform it to the pivot
|
||||
let pivot_pos = point.get_anchor_position(vector_data).map(|anchor_position| viewspace.transform_point2(anchor_position))?;
|
||||
let target = viewspace.transform_point2(point.get_position(vector_data)?);
|
||||
Some((pivot_pos, target))
|
||||
let (Some(pivot_pos), Some(position)) = (
|
||||
point.get_anchor_position(vector_data).map(|anchor_position| viewspace.transform_point2(anchor_position)),
|
||||
point.get_position(vector_data),
|
||||
) else {
|
||||
return (None, None);
|
||||
};
|
||||
let target = viewspace.transform_point2(position);
|
||||
(Some((pivot_pos, target)), None)
|
||||
}
|
||||
_ => {
|
||||
// Calculate the average position of all selected points
|
||||
let position = position();
|
||||
Some((position, position))
|
||||
(Some((position, position)), bounds)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -222,8 +247,17 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
|||
let affected_point_refs = affected_points.iter().collect();
|
||||
|
||||
let get_location = |point: &&ManipulatorPointId| point.get_position(&vector_data).map(|position| viewspace.transform_point2(position));
|
||||
if let Some((new_pivot, grab_target)) = calculate_pivot(&affected_point_refs, &vector_data, viewspace, |point: &ManipulatorPointId| get_location(&point), &self.dot) {
|
||||
if let (Some((new_pivot, grab_target)), bounds) = calculate_pivot(
|
||||
document,
|
||||
&affected_point_refs,
|
||||
&vector_data,
|
||||
viewspace,
|
||||
|point: &ManipulatorPointId| get_location(&point),
|
||||
&mut self.dot,
|
||||
selected_layers[0],
|
||||
) {
|
||||
*selected.pivot = new_pivot;
|
||||
self.path_bounds = bounds;
|
||||
|
||||
self.local_pivot = document_to_viewport.inverse().transform_point2(*selected.pivot);
|
||||
self.grab_target = document_to_viewport.inverse().transform_point2(grab_target);
|
||||
|
@ -249,117 +283,115 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
|||
return;
|
||||
}
|
||||
|
||||
for layer in document.metadata().all_layers() {
|
||||
if !document.network_interface.is_artboard(&layer.to_node(), &[]) {
|
||||
continue;
|
||||
};
|
||||
let viewport_box = input.viewport_bounds.size();
|
||||
let axis_constraint = self.transform_operation.axis_constraint();
|
||||
|
||||
let viewport_box = input.viewport_bounds.size();
|
||||
let axis_constraint = self.transform_operation.axis_constraint();
|
||||
let format_rounded = |value: f64, precision: usize| {
|
||||
if self.typing.digits.is_empty() || !self.transform_operation.can_begin_typing() {
|
||||
format!("{:.*}", precision, value).trim_end_matches('0').trim_end_matches('.').to_string()
|
||||
} else {
|
||||
self.typing.string.clone()
|
||||
}
|
||||
};
|
||||
|
||||
let format_rounded = |value: f64, precision: usize| {
|
||||
if self.typing.digits.is_empty() || !self.transform_operation.can_begin_typing() {
|
||||
format!("{:.*}", precision, value).trim_end_matches('0').trim_end_matches('.').to_string()
|
||||
} else {
|
||||
self.typing.string.clone()
|
||||
// TODO: Ensure removing this and adding this doesn't change the position of layers under PTZ ops
|
||||
// responses.add(TransformLayerMessage::PointerMove {
|
||||
// slow_key: SLOW_KEY,
|
||||
// increments_key: INCREMENTS_KEY,
|
||||
// });
|
||||
|
||||
match self.transform_operation {
|
||||
TransformOperation::None => (),
|
||||
TransformOperation::Grabbing(translation) => {
|
||||
let translation = translation.to_dvec(self.initial_transform, self.increments);
|
||||
let viewport_translate = document_to_viewport.transform_vector2(translation);
|
||||
let pivot = document_to_viewport.transform_point2(self.grab_target);
|
||||
let quad = Quad::from_box([pivot, pivot + viewport_translate]).0;
|
||||
let e1 = (self.layer_bounding_box.0[1] - self.layer_bounding_box.0[0]).normalize_or(DVec2::X);
|
||||
|
||||
if matches!(axis_constraint, Axis::Both | Axis::X) && translation.x != 0. {
|
||||
let end = if self.local { (quad[1] - quad[0]).rotate(e1) + quad[0] } else { quad[1] };
|
||||
overlay_context.dashed_line(quad[0], end, None, None, Some(2.), Some(2.), Some(0.5));
|
||||
|
||||
let x_transform = DAffine2::from_translation((quad[0] + end) / 2.);
|
||||
overlay_context.text(&format_rounded(translation.x, 3), COLOR_OVERLAY_BLUE, None, x_transform, 4., [Pivot::Middle, Pivot::End]);
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: Ensure removing this and adding this doesn't change the position of layers under PTZ ops
|
||||
// responses.add(TransformLayerMessage::PointerMove {
|
||||
// slow_key: SLOW_KEY,
|
||||
// increments_key: INCREMENTS_KEY,
|
||||
// });
|
||||
|
||||
match self.transform_operation {
|
||||
TransformOperation::None => (),
|
||||
TransformOperation::Grabbing(translation) => {
|
||||
let translation = translation.to_dvec(self.initial_transform, self.increments);
|
||||
let viewport_translate = document_to_viewport.transform_vector2(translation);
|
||||
let pivot = document_to_viewport.transform_point2(self.grab_target);
|
||||
let quad = Quad::from_box([pivot, pivot + viewport_translate]).0;
|
||||
let e1 = (self.layer_bounding_box.0[1] - self.layer_bounding_box.0[0]).normalize_or(DVec2::X);
|
||||
|
||||
if matches!(axis_constraint, Axis::Both | Axis::X) && translation.x != 0. {
|
||||
let end = if self.local { (quad[1] - quad[0]).rotate(e1) + quad[0] } else { quad[1] };
|
||||
overlay_context.dashed_line(quad[0], end, None, None, Some(2.), Some(2.), Some(0.5));
|
||||
|
||||
let x_transform = DAffine2::from_translation((quad[0] + end) / 2.);
|
||||
overlay_context.text(&format_rounded(translation.x, 3), COLOR_OVERLAY_BLUE, None, x_transform, 4., [Pivot::Middle, Pivot::End]);
|
||||
}
|
||||
|
||||
if matches!(axis_constraint, Axis::Both | Axis::Y) && translation.y != 0. {
|
||||
let end = if self.local { (quad[3] - quad[0]).rotate(e1) + quad[0] } else { quad[3] };
|
||||
overlay_context.dashed_line(quad[0], end, None, None, Some(2.), Some(2.), Some(0.5));
|
||||
let x_parameter = viewport_translate.x.clamp(-1., 1.);
|
||||
let y_transform = DAffine2::from_translation((quad[0] + end) / 2. + x_parameter * DVec2::X * 0.);
|
||||
let pivot_selection = if x_parameter >= -1e-3 { Pivot::Start } else { Pivot::End };
|
||||
if axis_constraint != Axis::Both || self.typing.digits.is_empty() || !self.transform_operation.can_begin_typing() {
|
||||
overlay_context.text(&format_rounded(translation.y, 2), COLOR_OVERLAY_BLUE, None, y_transform, 3., [pivot_selection, Pivot::Middle]);
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(axis_constraint, Axis::Both) && translation.x != 0. && translation.y != 0. {
|
||||
overlay_context.line(quad[1], quad[2], None, None);
|
||||
overlay_context.line(quad[3], quad[2], None, None);
|
||||
if matches!(axis_constraint, Axis::Both | Axis::Y) && translation.y != 0. {
|
||||
let end = if self.local { (quad[3] - quad[0]).rotate(e1) + quad[0] } else { quad[3] };
|
||||
overlay_context.dashed_line(quad[0], end, None, None, Some(2.), Some(2.), Some(0.5));
|
||||
let x_parameter = viewport_translate.x.clamp(-1., 1.);
|
||||
let y_transform = DAffine2::from_translation((quad[0] + end) / 2. + x_parameter * DVec2::X * 0.);
|
||||
let pivot_selection = if x_parameter >= -1e-3 { Pivot::Start } else { Pivot::End };
|
||||
if axis_constraint != Axis::Both || self.typing.digits.is_empty() || !self.transform_operation.can_begin_typing() {
|
||||
overlay_context.text(&format_rounded(translation.y, 2), COLOR_OVERLAY_BLUE, None, y_transform, 3., [pivot_selection, Pivot::Middle]);
|
||||
}
|
||||
}
|
||||
TransformOperation::Scaling(scale) => {
|
||||
let scale = scale.to_f64(self.increments);
|
||||
let text = format!("{}x", format_rounded(scale, 3));
|
||||
let pivot = document_to_viewport.transform_point2(self.local_pivot);
|
||||
let start_mouse = document_to_viewport.transform_point2(self.local_mouse_start);
|
||||
let local_edge = start_mouse - pivot;
|
||||
let local_edge = project_edge_to_quad(local_edge, &self.layer_bounding_box, self.local, axis_constraint);
|
||||
let boundary_point = pivot + local_edge * scale.min(1.);
|
||||
let end_point = pivot + local_edge * scale.max(1.);
|
||||
|
||||
if scale > 0. {
|
||||
overlay_context.dashed_line(pivot, boundary_point, None, None, Some(2.), Some(2.), Some(0.5));
|
||||
}
|
||||
overlay_context.line(boundary_point, end_point, None, None);
|
||||
|
||||
let transform = DAffine2::from_translation(boundary_point.midpoint(pivot) + local_edge.perp().normalize_or(DVec2::X) * local_edge.element_product().signum() * 24.);
|
||||
overlay_context.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);
|
||||
}
|
||||
TransformOperation::Rotating(rotation) => {
|
||||
let angle = rotation.to_f64(self.increments);
|
||||
let pivot = document_to_viewport.transform_point2(self.local_pivot);
|
||||
let start_mouse = document_to_viewport.transform_point2(self.local_mouse_start);
|
||||
let offset_angle = if self.grs_pen_handle {
|
||||
self.handle - self.last_point
|
||||
} else if using_path_tool {
|
||||
start_mouse - pivot
|
||||
} else {
|
||||
self.layer_bounding_box.top_right() - self.layer_bounding_box.top_right()
|
||||
};
|
||||
let tilt_offset = document.document_ptz.unmodified_tilt();
|
||||
let offset_angle = offset_angle.to_angle() + tilt_offset;
|
||||
let width = viewport_box.max_element();
|
||||
let radius = start_mouse.distance(pivot);
|
||||
let arc_radius = ANGLE_MEASURE_RADIUS_FACTOR * width;
|
||||
let radius = radius.clamp(ARC_MEASURE_RADIUS_FACTOR_RANGE.0 * width, ARC_MEASURE_RADIUS_FACTOR_RANGE.1 * width);
|
||||
let angle_in_degrees = angle.to_degrees();
|
||||
let display_angle = if angle_in_degrees.is_sign_positive() {
|
||||
angle_in_degrees - (angle_in_degrees / 360.).floor() * 360.
|
||||
} else if angle_in_degrees.is_sign_negative() {
|
||||
angle_in_degrees - ((angle_in_degrees / 360.).floor() + 1.) * 360.
|
||||
} else {
|
||||
angle_in_degrees
|
||||
};
|
||||
let text = format!("{}°", format_rounded(display_angle, 2));
|
||||
let text_texture_width = overlay_context.get_width(&text) / 2.;
|
||||
let text_texture_height = 12.;
|
||||
let text_angle_on_unit_circle = DVec2::from_angle((angle % TAU) / 2. + offset_angle);
|
||||
let text_texture_position = DVec2::new(
|
||||
(arc_radius + 4. + text_texture_width) * text_angle_on_unit_circle.x,
|
||||
(arc_radius + text_texture_height) * text_angle_on_unit_circle.y,
|
||||
);
|
||||
let transform = DAffine2::from_translation(text_texture_position + pivot);
|
||||
overlay_context.draw_angle(pivot, radius, arc_radius, offset_angle, angle);
|
||||
overlay_context.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);
|
||||
if matches!(axis_constraint, Axis::Both) && translation.x != 0. && translation.y != 0. {
|
||||
overlay_context.line(quad[1], quad[2], None, None);
|
||||
overlay_context.line(quad[3], quad[2], None, None);
|
||||
}
|
||||
}
|
||||
TransformOperation::Scaling(scale) => {
|
||||
let scale = scale.to_f64(self.increments);
|
||||
let text = format!("{}x", format_rounded(scale, 3));
|
||||
let pivot = document_to_viewport.transform_point2(self.local_pivot);
|
||||
let start_mouse = document_to_viewport.transform_point2(self.local_mouse_start);
|
||||
let local_edge = start_mouse - pivot;
|
||||
let local_edge = project_edge_to_quad(local_edge, &self.layer_bounding_box, self.local, axis_constraint);
|
||||
let boundary_point = pivot + local_edge * scale.min(1.);
|
||||
let end_point = pivot + local_edge * scale.max(1.);
|
||||
|
||||
if scale > 0. {
|
||||
overlay_context.dashed_line(pivot, boundary_point, None, None, Some(2.), Some(2.), Some(0.5));
|
||||
}
|
||||
overlay_context.line(boundary_point, end_point, None, None);
|
||||
|
||||
let transform = DAffine2::from_translation(boundary_point.midpoint(pivot) + local_edge.perp().normalize_or(DVec2::X) * local_edge.element_product().signum() * 24.);
|
||||
overlay_context.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);
|
||||
}
|
||||
TransformOperation::Rotating(rotation) => {
|
||||
let angle = rotation.to_f64(self.increments);
|
||||
let pivot = document_to_viewport.transform_point2(self.local_pivot);
|
||||
let start_mouse = document_to_viewport.transform_point2(self.local_mouse_start);
|
||||
let offset_angle = if self.grs_pen_handle {
|
||||
self.handle - self.last_point
|
||||
} else if using_path_tool {
|
||||
start_mouse - pivot
|
||||
} else {
|
||||
self.layer_bounding_box.top_right() - self.layer_bounding_box.top_right()
|
||||
};
|
||||
let tilt_offset = document.document_ptz.unmodified_tilt();
|
||||
let offset_angle = offset_angle.to_angle() + tilt_offset;
|
||||
let width = viewport_box.max_element();
|
||||
let radius = start_mouse.distance(pivot);
|
||||
let arc_radius = ANGLE_MEASURE_RADIUS_FACTOR * width;
|
||||
let radius = radius.clamp(ARC_MEASURE_RADIUS_FACTOR_RANGE.0 * width, ARC_MEASURE_RADIUS_FACTOR_RANGE.1 * width);
|
||||
let angle_in_degrees = angle.to_degrees();
|
||||
let display_angle = if angle_in_degrees.is_sign_positive() {
|
||||
angle_in_degrees - (angle_in_degrees / 360.).floor() * 360.
|
||||
} else if angle_in_degrees.is_sign_negative() {
|
||||
angle_in_degrees - ((angle_in_degrees / 360.).floor() + 1.) * 360.
|
||||
} else {
|
||||
angle_in_degrees
|
||||
};
|
||||
let text = format!("{}°", format_rounded(display_angle, 2));
|
||||
let text_texture_width = overlay_context.get_width(&text) / 2.;
|
||||
let text_texture_height = 12.;
|
||||
let text_angle_on_unit_circle = DVec2::from_angle((angle % TAU) / 2. + offset_angle);
|
||||
let text_texture_position = DVec2::new(
|
||||
(arc_radius + 4. + text_texture_width) * text_angle_on_unit_circle.x,
|
||||
(arc_radius + text_texture_height) * text_angle_on_unit_circle.y,
|
||||
);
|
||||
let transform = DAffine2::from_translation(text_texture_position + pivot);
|
||||
overlay_context.draw_angle(pivot, radius, arc_radius, offset_angle, angle);
|
||||
overlay_context.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(bounds) = self.path_bounds {
|
||||
overlay_context.quad(Quad::from_box(bounds), None, None);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue