use crate::font::Fonts;
use super::Editor;
use super::commands::{Command, Outcome};
use super::input::Request;
use super::position::Position;
use super::selection::Selection;
#[derive(Clone, Copy, Debug)]
pub(super) struct TextDrag {
pub start: (f64, f64),
pub press: Position,
pub moved: bool,
pub copy: bool,
pub drop: Option<Position>,
}
impl Editor {
pub(super) fn inside_selection(&self, p: &Position) -> bool {
let Selection::Text { .. } = self.selection else { return false };
let Some((a, b)) = self.selection.ordered(&self.index) else { return false };
self.index.cmp(&a, p) == std::cmp::Ordering::Less && self.index.cmp(p, &b) == std::cmp::Ordering::Less
}
pub(super) fn text_drag_move(&mut self, x: f64, y: f64, ctrl: bool, fonts: &Fonts) {
let Some(mut d) = self.text_drag else { return };
let tol = 2.25 / self.viewport.zoom.max(0.1);
if !d.moved && ((x - d.start.0).abs() > tol || (y - d.start.1).abs() > tol) {
d.moved = true;
}
if d.moved {
d.drop = self.hit(x, y, fonts);
d.copy |= ctrl;
}
self.text_drag = Some(d);
}
pub(super) fn text_drag_release(&mut self, fonts: &Fonts) -> Option<Outcome> {
let d = self.text_drag.take()?;
self.requests.push(Request::ReleasePointer);
if !d.moved {
self.set_selection(Selection::Caret(d.press));
return Some(Outcome::Moved);
}
let Some(drop) = d.drop else { return Some(Outcome::Unchanged("no drop place")) };
let Some((a, b)) = self.selection.ordered(&self.index) else { return Some(Outcome::Unchanged("no selection")) };
let in_box = match drop.story {
super::position::StoryId::TextBox(f) => self.index.float_anchor(f).and_then(|para| {
let p = self.index.para(&self.doc, para)?;
let k = p.inlines.iter().position(|i| matches!(i, crate::docx::Inline::Float(x, _) if crate::layout::FloatId(x.id) == f))?;
let u: u32 = p.inlines[..k].iter().map(|i| i.units() as u32).sum();
let story = self.index.para_slot(para).map(|s| self.index.story(s.container))?;
Some(Position::text(story, para, crate::layout::Unit(u)))
}),
_ => None,
};
let inside = |p: &Position| self.index.cmp(&a, p) != std::cmp::Ordering::Greater && self.index.cmp(p, &b) != std::cmp::Ordering::Greater;
if inside(&drop) || in_box.is_some_and(|p| inside(&p)) {
return Some(Outcome::Unchanged("dropped on the selection"));
}
let frag = self.copy_fragment()?;
let sel = self.selection;
let mut placed: Option<(Position, Position)> = None;
let paste = |e: &mut Self, placed: &mut Option<(Position, Position)>| {
e.set_selection(Selection::Caret(drop));
let start = drop;
let ok = matches!(e.paste_fragment(&frag, fonts), Outcome::Changed(_));
if ok && let Some(end) = e.selection.focus() {
*placed = Some((start, end));
}
ok
};
let delete = |e: &mut Self, placed: &mut Option<(Position, Position)>| {
e.set_selection(sel);
e.smart_cut();
let ok = matches!(e.run(Command::Delete { dir: super::commands::Dir::Forward, word: false }, fonts), Outcome::Changed(_));
if ok && let Some((s, t)) = placed.as_mut() {
for m in &e.last_maps {
(*s, *t) = (m.apply(*s), m.apply(*t));
}
}
ok
};
let after = self.index.cmp(&drop, &b) == std::cmp::Ordering::Greater;
#[allow(clippy::if_same_then_else, reason = "the order differs: each call moves what the other one sees")]
let steps = if d.copy {
paste(self, &mut placed) as usize
} else if after {
paste(self, &mut placed) as usize + delete(self, &mut placed) as usize
} else {
delete(self, &mut placed) as usize + paste(self, &mut placed) as usize
};
if steps == 0 {
return Some(Outcome::Unchanged("nothing dropped"));
}
if let Some((s, t)) = placed {
self.selection = Selection::range(s, t, super::selection::Granularity::Char);
}
self.history.merge_last(steps, if d.copy { "Copy" } else { "Move" });
Some(Outcome::Changed(self.revision))
}
}