mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-25 18:47:07 -06:00
UI: let Type tool edit selected overlapping text while respecting visibility
(cherry picked from commit 9fd5b1e5618a6ad19638494b480460e9527debde) (cherry picked from commit 812e34fd7834e828bebd3f79440f831d188a1a99) (cherry picked from commit 283adb1a30e6b51ab6603488e57b9997a0ca83f0)
This commit is contained in:
@@ -117,12 +117,23 @@ fn edited(app: &EffectcraftApp, ectx: &EvalCtx, lid: LayerId) -> Option<Edited>
|
||||
Some(Edited { layer, doc, lay, m, inv, carets })
|
||||
}
|
||||
|
||||
/// The topmost text layer whose text is under screen point `s`.
|
||||
fn text_layer_at(app: &EffectcraftApp, ectx: &EvalCtx, map: &ViewerMap, s: Pos2) -> Option<Edited> {
|
||||
super::viewer::selectable_layers(ectx.comp, ectx.time)
|
||||
/// Type-tool clicks prefer a selected text layer, as viewer dragging does (#230).
|
||||
/// Double-clicks retain topmost picking. Both use the same visibility/lock/time/solo filter.
|
||||
fn text_layer_at(app: &EffectcraftApp, ectx: &EvalCtx, map: &ViewerMap, s: Pos2, prefer_selected: bool) -> Option<Edited> {
|
||||
let mut topmost = None;
|
||||
for e in super::viewer::selectable_layers(ectx.comp, ectx.time)
|
||||
.filter(|l| matches!(l.source, LayerSource::Text))
|
||||
.filter_map(|l| edited(app, ectx, l.id))
|
||||
.find(|e| e.contains(map, s))
|
||||
.filter(|e| e.contains(map, s))
|
||||
{
|
||||
if !prefer_selected || app.session.state.selected_layers.contains(&e.layer.id) {
|
||||
return Some(e);
|
||||
}
|
||||
if topmost.is_none() {
|
||||
topmost = Some(e);
|
||||
}
|
||||
}
|
||||
topmost
|
||||
}
|
||||
|
||||
fn exec(app: &mut EffectcraftApp, id: &str, p: Value) -> Option<Value> {
|
||||
@@ -207,7 +218,7 @@ pub fn hook(
|
||||
}
|
||||
}
|
||||
if !handled && type_tool {
|
||||
if let Some(e) = text_layer_at(app, ectx, map, pos) {
|
||||
if let Some(e) = text_layer_at(app, ectx, map, pos, true) {
|
||||
let ci = e.hit(map, pos);
|
||||
exec(app, "text.edit", json!({"layer": e.layer.id.0, "caret": ci}));
|
||||
drag = Some(Drag::Select { layer: e.layer.id });
|
||||
@@ -309,7 +320,7 @@ pub fn hook(
|
||||
owns = true;
|
||||
}
|
||||
_ if !matches!(tool, Tool::Hand | Tool::Zoom) && !tool.is_pen() && !type_tool => {
|
||||
if let Some(e) = text_layer_at(app, ectx, map, p) {
|
||||
if let Some(e) = text_layer_at(app, ectx, map, p, false) {
|
||||
exec(app, "text.edit", json!({"layer": e.layer.id.0}));
|
||||
ui.memory_mut(|m| m.request_focus(focus_id()));
|
||||
owns = true;
|
||||
|
||||
@@ -342,3 +342,44 @@ fn overlapping_duplicate_text_remains_editable_by_double_click_and_selected_type
|
||||
key(&mut h, Key::Z, Modifiers::COMMAND);
|
||||
assert_eq!(edited(&h).1, "alpha beta");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_tool_selected_text_preference_skips_ineligible_layers_and_falls_back_to_topmost() {
|
||||
for exclusion in ["hidden", "locked", "inactive", "unsoloed", "outside", "unselected"] {
|
||||
let mut a = app();
|
||||
let mut ids = vec![];
|
||||
for _ in 0..2 {
|
||||
ids.push(
|
||||
a.session.execute("layer.newText", json!({"text": "alpha beta", "size": 40, "box": [40, 60, 250, 100]})).unwrap()["layer"].as_u64().unwrap(),
|
||||
);
|
||||
}
|
||||
let original = ids[0];
|
||||
let topmost = ids[1];
|
||||
match exclusion {
|
||||
"hidden" | "locked" => {
|
||||
let switch = if exclusion == "hidden" { "video" } else { "locked" };
|
||||
a.session.execute("layer.setSwitch", json!({"layers": [original], "switch": switch, "value": exclusion == "locked"})).unwrap();
|
||||
}
|
||||
"inactive" => {
|
||||
let cid = a.session.active_comp_id().unwrap();
|
||||
std::sync::Arc::make_mut(&mut a.session.project).comp_mut(cid).unwrap().layer_mut(LayerId(original)).unwrap().out_point =
|
||||
effectcraft_engine::time::Tick::ZERO;
|
||||
}
|
||||
"unsoloed" => {
|
||||
a.session.execute("layer.setSwitch", json!({"layers": [topmost], "switch": "solo", "value": true})).unwrap();
|
||||
}
|
||||
"outside" => {
|
||||
a.session.execute("prop.set", json!({"layer": original, "path": "transform/position", "value": [500, 300]})).unwrap();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
a.session.state.selected_layers = if exclusion == "unselected" { vec![] } else { vec![LayerId(original)] };
|
||||
a.ui.tool = effectcraft_ui_egui::state::Tool::Type;
|
||||
let mut h = Harness::builder().with_size(egui::vec2(1600.0, 1000.0)).with_step_dt(1.0 / 60.0).build_eframe(move |_| a);
|
||||
h.run_steps(3);
|
||||
let comp = rect(&h, "viewer.comp");
|
||||
let p = comp.min + egui::vec2(80.0, 85.0) * (comp.width() / 640.0);
|
||||
click(&mut h, p, Modifiers::NONE);
|
||||
assert_eq!(edited(&h).0, topmost, "{exclusion} selected text must not override the eligible hit");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user