Add quick measure overlays with Alt pressed (#1894)

* Measure with alt

* Code review

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
James Lindsay 2024-08-10 12:56:02 +01:00 committed by GitHub
parent 501b562d0f
commit 1f278799d6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 93 additions and 7 deletions

View File

@ -78,7 +78,9 @@ impl OverlayContext {
let position = position.round() - DVec2::splat(0.5);
self.render_context.begin_path();
self.render_context.arc(position.x, position.y, MANIPULATOR_GROUP_MARKER_SIZE / 2., 0., TAU).expect("draw circle");
self.render_context
.arc(position.x, position.y, MANIPULATOR_GROUP_MARKER_SIZE / 2., 0., TAU)
.expect("Failed to draw the circle");
let fill = if selected { COLOR_OVERLAY_BLUE } else { COLOR_OVERLAY_WHITE };
self.render_context.set_fill_style(&wasm_bindgen::JsValue::from_str(fill));
@ -127,7 +129,7 @@ impl OverlayContext {
let color_stroke = color_stroke.unwrap_or(COLOR_OVERLAY_BLUE);
let position = position.round();
self.render_context.begin_path();
self.render_context.arc(position.x, position.y, radius, 0., TAU).expect("draw circle");
self.render_context.arc(position.x, position.y, radius, 0., TAU).expect("Failed to draw the circle");
self.render_context.set_fill_style(&wasm_bindgen::JsValue::from_str(color_fill));
self.render_context.set_stroke_style(&wasm_bindgen::JsValue::from_str(color_stroke));
self.render_context.fill();
@ -139,7 +141,7 @@ impl OverlayContext {
// Circle
self.render_context.begin_path();
self.render_context.arc(x, y, PIVOT_DIAMETER / 2., 0., TAU).expect("draw circle");
self.render_context.arc(x, y, PIVOT_DIAMETER / 2., 0., TAU).expect("Failed to draw the circle");
self.render_context.set_fill_style(&wasm_bindgen::JsValue::from_str(COLOR_OVERLAY_YELLOW));
self.render_context.fill();
@ -247,7 +249,7 @@ impl OverlayContext {
pub fn text(&self, text: &str, pos: DVec2, background: &str, padding: f64) {
let pos = pos.round();
let metrics = self.render_context.measure_text(text).expect("measure text");
let metrics = self.render_context.measure_text(text).expect("Failed to measure the text dimensions");
self.render_context.set_fill_style(&background.into());
self.render_context.fill_rect(
pos.x + metrics.actual_bounding_box_left(),
@ -258,6 +260,34 @@ impl OverlayContext {
self.render_context.set_fill_style(&"white".into());
self.render_context
.fill_text(text, pos.x + padding, pos.y - padding - metrics.font_bounding_box_descent())
.expect("draw text");
.expect("Failed to draw the text on the canvas");
}
pub fn angle_text(&self, text: &str, pos: DVec2, direction: DVec2, padding: f64, pivot: Pivot) {
self.render_context.translate(pos.x, pos.y).expect("Failed to translate the render context to the specified position");
let angle = -direction.angle_to(DVec2::X);
self.render_context.rotate(angle).expect("Failed to rotate the render context to the specified angle");
let metrics = self.render_context.measure_text(text).expect("Failed to measure the text dimensions");
self.render_context.set_fill_style(&wasm_bindgen::JsValue::from_str(COLOR_OVERLAY_BLUE));
let local_position = match pivot {
Pivot::LeftCentreY => DVec2::new(padding, (metrics.actual_bounding_box_ascent() + metrics.actual_bounding_box_descent()) / 2.),
Pivot::TopCentreX => DVec2::new(
-(metrics.actual_bounding_box_right() + metrics.actual_bounding_box_left()) / 2.,
padding + metrics.font_bounding_box_ascent(),
),
};
self.render_context
.fill_text(text, local_position.x, local_position.y)
.expect("Failed to draw the text at the calculated position");
self.render_context.reset_transform().expect("Failed to reset the render context transform");
}
}
pub enum Pivot {
LeftCentreY,
TopCentreX,
}

View File

@ -0,0 +1,36 @@
use crate::messages::portfolio::document::overlays::utility_types::{self, OverlayContext};
use crate::messages::tool::tool_messages::tool_prelude::*;
use graphene_std::renderer::Rect;
pub fn overlay(selected_bounds: Rect, hovered_bounds: Rect, transform: DAffine2, document_to_viewport: DAffine2, overlay_context: &mut OverlayContext) {
let transform_to_document = document_to_viewport.inverse() * transform;
if selected_bounds.intersects(hovered_bounds) {
// TODO: I'm not sure what to do here?
return;
}
// Always do horizontal then vertical from the selected
let turn_x = selected_bounds.center().x.clamp(hovered_bounds.min().x, hovered_bounds.max().x);
let turn_y = hovered_bounds.center().y.clamp(selected_bounds.min().y, selected_bounds.max().y);
let selected_x = turn_x.clamp(selected_bounds.min().x, selected_bounds.max().x);
let hovered_y = turn_y.clamp(hovered_bounds.min().y, hovered_bounds.max().y);
if turn_x != selected_x {
let min_viewport = transform.transform_point2(DVec2::new(turn_x.min(selected_x), turn_y));
let max_viewport = transform.transform_point2(DVec2::new(turn_x.max(selected_x), turn_y));
overlay_context.line(min_viewport, max_viewport);
let length = format!("{:.2}", transform_to_document.transform_vector2(DVec2::X * (turn_x - selected_x)).length());
let direction = -(min_viewport - max_viewport).normalize_or_zero();
overlay_context.angle_text(&length, (min_viewport + max_viewport) / 2., direction, 5., utility_types::Pivot::TopCentreX);
}
if turn_y != hovered_y {
let min_viewport = transform.transform_point2(DVec2::new(turn_x, turn_y.min(hovered_y)));
let max_viewport = transform.transform_point2(DVec2::new(turn_x, turn_y.max(hovered_y)));
overlay_context.line(min_viewport, max_viewport);
let length = format!("{:.2}", transform_to_document.transform_vector2(DVec2::Y * (turn_y - hovered_y)).length());
let direction = (min_viewport - max_viewport).normalize_or_zero().perp();
overlay_context.angle_text(&length, (min_viewport + max_viewport) / 2., direction, 5., utility_types::Pivot::LeftCentreY);
}
}

View File

@ -1,6 +1,7 @@
pub mod auto_panning;
pub mod color_selector;
pub mod graph_modification_utils;
pub mod measure;
pub mod pivot;
pub mod resize;
pub mod shape_editor;

View File

@ -486,6 +486,7 @@ impl Fsm for PathToolFsmState {
// `Self::InsertPoint` case:
(Self::InsertPoint, PathToolMessage::MouseDown { .. } | PathToolMessage::Enter { .. }) => {
tool_data.double_click_handled = true;
// TODO: Don't use `Key::Shift` directly, instead take it as a variable from the input mappings list like in all other places
let shift = input.keyboard.get(Key::Shift as usize);
tool_data.end_insertion(shape_editor, responses, InsertEndKind::Add { shift })
}
@ -499,8 +500,11 @@ impl Fsm for PathToolFsmState {
(Self::InsertPoint, PathToolMessage::GRS { key: propagate }) => {
// MAYBE: use `InputMapperMessage::KeyDown(..)` instead
match propagate {
// TODO: Don't use `Key::G` directly, instead take it as a variable from the input mappings list like in all other places
Key::KeyG => responses.add(TransformLayerMessage::BeginGrab),
// TODO: Don't use `Key::R` directly, instead take it as a variable from the input mappings list like in all other places
Key::KeyR => responses.add(TransformLayerMessage::BeginRotate),
// TODO: Don't use `Key::S` directly, instead take it as a variable from the input mappings list like in all other places
Key::KeyS => responses.add(TransformLayerMessage::BeginScale),
_ => warn!("Unexpected GRS key"),
}

View File

@ -10,14 +10,15 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis};
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, NodeNetworkInterface, NodeTemplate};
use crate::messages::portfolio::document::utility_types::transformation::Selected;
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
use crate::messages::tool::common_functionality::graph_modification_utils::is_layer_fed_by_node_of_name;
use crate::messages::tool::common_functionality::pivot::Pivot;
use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData, SnapManager};
use crate::messages::tool::common_functionality::transformation_cage::*;
use crate::messages::tool::common_functionality::{auto_panning::AutoPanning, measure};
use graph_craft::document::NodeId;
use graphene_core::renderer::Quad;
use graphene_std::renderer::Rect;
use graphene_std::vector::misc::BooleanOperation;
use std::fmt;
@ -471,13 +472,27 @@ impl Fsm for SelectToolFsmState {
// Update the selection box
overlay_context.quad(quad);
}
// Only highlight layers if the viewport is not being panned (middle mouse button is pressed)
} else if !input.keyboard.get(Key::Mmb as usize) {
// TODO: Don't use `Key::Mmb` directly, instead take it as a variable from the input mappings list like in all other places
else if !input.keyboard.get(Key::Mmb as usize) {
// Get the layer the user is hovering over
let click = document.click(input);
let not_selected_click = click.filter(|&hovered_layer| !document.network_interface.selected_nodes(&[]).unwrap().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));
// Measure with Alt held down
// TODO: Don't use `Key::Alt` directly, instead take it as a variable from the input mappings list like in all other places
if input.keyboard.get(Key::Alt as usize) {
let hovered_bounds = document
.metadata()
.bounding_box_with_transform(layer, transform.inverse() * document.metadata().transform_to_viewport(layer));
if let [Some(selected_bounds), Some(hovered_bounds)] = [bounds, hovered_bounds].map(|rect| rect.map(Rect::from_box)) {
measure::overlay(selected_bounds, hovered_bounds, transform, document.metadata().document_to_viewport, &mut overlay_context);
}
}
}
}