mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-14 19:27:05 -04: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) (cherry picked from commit cb2e659baad5e4fe853c124dafe32491335b8232)
This commit is contained in:
1 parent
66812324c9
commit
9af5560c1f
2 files changed
+58
-6
No files matched your search
@@ -118,12 +118,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> {
|
||||
@@ -223,7 +234,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 });
|
||||
@@ -325,7 +336,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;
|
||||
|
||||
@@ -529,3 +529,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