forked from 1b-agent/opencadstudio-upstream
feat(viewport): Ctrl+click opens an entity PE_URL hyperlink
Entities that carry the standard PE_URL XDATA record (what HYPERLINK writes) can now be followed from the canvas: Ctrl+click opens the link in the system browser and consumes the click, so no box/lasso selection starts. - scene::selection::pe_url_of() reads the record with the same rule as the Properties panel hyperlink field; unit-tested. - The ViewCube/compass hit region wins over a hyperlink drawn beneath it. - Unchanged behaviour when Ctrl is not held or the entity has no link.
This commit is contained in:
1 parent
05d95e2e72
commit
171fc48987
3 files changed
+116
No files matched your search
@@ -3029,6 +3029,67 @@ impl OpenCADStudio {
|
||||
};
|
||||
let (vw, vh) = vp_size;
|
||||
|
||||
// Ctrl+click on an entity carrying a standard `PE_URL` hyperlink opens
|
||||
// that link in the system browser (the AutoCAD behaviour). Only in
|
||||
// model space with no command running; a hit consumes the click, so no
|
||||
// box/lasso selection starts. Anything else (no Ctrl, no link) falls
|
||||
// through to the existing logic untouched.
|
||||
if self.ctrl_down
|
||||
&& self.tabs[i].active_cmd.is_none()
|
||||
&& self.tabs[i].scene.current_layout == "Model"
|
||||
{
|
||||
// Match the selection pick below: map the cursor into the active
|
||||
// model tile and use a zero-origin bounds rectangle.
|
||||
let tile_b = self.tabs[i].scene.active_model_tile_bounds(vw, vh);
|
||||
let local = iced::Point {
|
||||
x: p.x - tile_b.x,
|
||||
y: p.y - tile_b.y,
|
||||
};
|
||||
let bounds = iced::Rectangle {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
width: tile_b.width,
|
||||
height: tile_b.height,
|
||||
};
|
||||
let (view_rot, eye) = {
|
||||
let cam = self.tabs[i].scene.camera.borrow();
|
||||
(cam.view_proj_rte(bounds), cam.eye())
|
||||
};
|
||||
// The ViewCube/compass gizmo is drawn over the top-right corner:
|
||||
// clicks inside its hit region belong to the gizmo, never to a
|
||||
// hyperlink drawn underneath it.
|
||||
let (ccx, ccy, cw, ch) = match self.tabs[i]
|
||||
.scene
|
||||
.active_viewport
|
||||
.and_then(|hndl| self.tabs[i].scene.viewport_screen_rect(hndl, (vw, vh)))
|
||||
{
|
||||
Some(rect) => (p.x - rect.x, p.y - rect.y, rect.width, rect.height),
|
||||
None => (local.x, local.y, tile_b.width, tile_b.height),
|
||||
};
|
||||
let on_gizmo = scene::hit_test(ccx, ccy, cw, ch, view_rot, VIEWCUBE_PX).is_some();
|
||||
let wires = self.tabs[i].scene.hit_test_wires();
|
||||
let hit = if on_gizmo {
|
||||
None
|
||||
} else {
|
||||
scene::pick::hit_test::click_hit(
|
||||
local,
|
||||
&*wires,
|
||||
view_rot,
|
||||
eye,
|
||||
bounds,
|
||||
self.tabs[i].scene.document.header.lineweight_display,
|
||||
crate::ui::overlay::pick_box_aperture_px(self.pick_box),
|
||||
)
|
||||
.and_then(|s| Scene::handle_from_wire_name(s))
|
||||
};
|
||||
if let Some(url) =
|
||||
hit.and_then(|handle| scene::pe_url_of(&self.tabs[i].scene.document, handle))
|
||||
{
|
||||
self.command_line.push_info(&format!("Opening hyperlink: {url}"));
|
||||
return crate::sys::open_url(&url, self.main_window);
|
||||
}
|
||||
}
|
||||
|
||||
// An engaged grip owns the next left press (click-move-click placement
|
||||
// or the release of a press-drag). Do not let the same press arm the
|
||||
// normal box/lasso state or trigger another viewport control; the
|
||||
|
||||
@@ -46,6 +46,7 @@ mod preview;
|
||||
mod project;
|
||||
mod scene_markers;
|
||||
mod selection;
|
||||
pub use selection::pe_url_of;
|
||||
pub mod sketch_constraints;
|
||||
mod sketch_persist;
|
||||
mod sketch_solve;
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
use super::*;
|
||||
|
||||
/// The entity's standard `PE_URL` hyperlink (the record AutoCAD's HYPERLINK
|
||||
/// command writes).
|
||||
///
|
||||
/// Returns the first non-empty string value of the record; `None` when the
|
||||
/// entity or record is missing, the record holds no string, or it is empty.
|
||||
/// Matches how the Properties panel reads its `"hyperlink"` field.
|
||||
pub fn pe_url_of(doc: &CadDocument, handle: Handle) -> Option<String> {
|
||||
doc.get_entity(handle)
|
||||
.and_then(|entity| entity.common().extended_data.get_record("PE_URL"))
|
||||
.and_then(|record| {
|
||||
record.values.iter().find_map(|value| match value {
|
||||
acadrust::xdata::XDataValue::String(text) if !text.is_empty() => Some(text.clone()),
|
||||
_ => None,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
impl Scene {
|
||||
// ── Selection ─────────────────────────────────────────────────────────
|
||||
/// Treat a classic LEADER and its attached annotation as one logical object.
|
||||
@@ -1116,6 +1133,43 @@ impl Scene {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn pe_url_of_reads_standard_hyperlink_xdata() {
|
||||
use acadrust::entities::Point;
|
||||
use acadrust::xdata::{ExtendedDataRecord, XDataValue};
|
||||
|
||||
let mut doc = CadDocument::new();
|
||||
|
||||
// An entity carrying a PE_URL hyperlink resolves to that link.
|
||||
let mut linked = Point::new();
|
||||
let mut rec = ExtendedDataRecord::new("PE_URL");
|
||||
rec.add_value(XDataValue::String("https://example.com/gui".to_string()));
|
||||
linked.common.extended_data.add_record(rec);
|
||||
let linked_handle = doc
|
||||
.add_entity(EntityType::Point(linked))
|
||||
.expect("linked entity added");
|
||||
assert_eq!(
|
||||
pe_url_of(&doc, linked_handle).as_deref(),
|
||||
Some("https://example.com/gui")
|
||||
);
|
||||
|
||||
// No PE_URL record -> None.
|
||||
let plain_handle = doc
|
||||
.add_entity(EntityType::Point(Point::new()))
|
||||
.expect("plain entity added");
|
||||
assert_eq!(pe_url_of(&doc, plain_handle), None);
|
||||
|
||||
// Empty string in the record -> None.
|
||||
let mut empty = Point::new();
|
||||
let mut empty_rec = ExtendedDataRecord::new("PE_URL");
|
||||
empty_rec.add_value(XDataValue::String(String::new()));
|
||||
empty.common.extended_data.add_record(empty_rec);
|
||||
let empty_handle = doc
|
||||
.add_entity(EntityType::Point(empty))
|
||||
.expect("empty-link entity added");
|
||||
assert_eq!(pe_url_of(&doc, empty_handle), None);
|
||||
}
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in new issue
Block a user