mirror of
https://github.com/flectar/mail.git
synced 2026-11-12 10:15:19 +00:00
349 lines
13 KiB
Rust
349 lines
13 KiB
Rust
//! One document input route for links, selection and reader controls.
|
|
use super::*;
|
|
use crate::renderer::InputModifiers;
|
|
fn banner_height(app: &AppWindow) -> f32 {
|
|
if app.get_remote_images_blocked()
|
|
&& !app.get_text_mode()
|
|
&& !app.get_source_mode()
|
|
&& !app.global::<EmailReader>().get_reader_mode()
|
|
{
|
|
60.0
|
|
} else {
|
|
0.0
|
|
}
|
|
}
|
|
|
|
fn repaint_reader(app: &AppWindow, renderer: &Rc<RefCell<GpuEmailRenderer>>, gpu: bool) {
|
|
repaint_reader_inner(app, renderer, gpu, false);
|
|
}
|
|
|
|
fn repaint_reader_inner(
|
|
app: &AppWindow,
|
|
renderer: &Rc<RefCell<GpuEmailRenderer>>,
|
|
gpu: bool,
|
|
incremental: bool,
|
|
) {
|
|
if !gpu {
|
|
let (width, height) = email_viewport_size(app);
|
|
let result = if incremental {
|
|
renderer
|
|
.borrow_mut()
|
|
.render_cpu_scroll_step(width, height, app.window().scale_factor())
|
|
} else {
|
|
renderer
|
|
.borrow_mut()
|
|
.render_cpu_if_needed(width, height, app.window().scale_factor())
|
|
};
|
|
match result {
|
|
Ok(Some(frame)) => apply_cpu_frame(app, frame),
|
|
Ok(None) => {}
|
|
Err(error) => {
|
|
app.global::<EmailReader>().set_notice(error.into());
|
|
app.global::<EmailReader>().set_reader_mode(false);
|
|
app.set_text_mode(true);
|
|
}
|
|
}
|
|
}
|
|
sync_reader_metadata(app, renderer);
|
|
if !renderer.borrow().selection_active() {
|
|
update_email_selection(app, renderer);
|
|
}
|
|
app.window().request_redraw();
|
|
}
|
|
|
|
fn schedule_scroll_frame(
|
|
app: &AppWindow,
|
|
renderer: &Rc<RefCell<GpuEmailRenderer>>,
|
|
gpu: bool,
|
|
pending: &Rc<Cell<bool>>,
|
|
) {
|
|
if pending.replace(true) {
|
|
return;
|
|
}
|
|
let weak = app.as_weak();
|
|
let renderer = renderer.clone();
|
|
let pending = pending.clone();
|
|
Timer::single_shot(Duration::from_millis(16), move || {
|
|
pending.set(false);
|
|
if let Some(app) = weak.upgrade() {
|
|
repaint_reader_inner(&app, &renderer, gpu, true);
|
|
if !gpu && renderer.borrow().needs_repaint() {
|
|
schedule_scroll_frame(&app, &renderer, gpu, &pending);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
pub(super) fn register_renderer_input_callbacks(
|
|
app: &AppWindow,
|
|
renderer: &Rc<RefCell<GpuEmailRenderer>>,
|
|
gpu: bool,
|
|
opener: ExternalUrlOpener,
|
|
) {
|
|
let weak = app.as_weak();
|
|
let appearance_renderer = renderer.clone();
|
|
let appearance_pending = Rc::new(Cell::new(false));
|
|
app.global::<MessageColors>().on_refresh(move || {
|
|
if appearance_pending.replace(true) {
|
|
return;
|
|
}
|
|
let weak = weak.clone();
|
|
let renderer = appearance_renderer.clone();
|
|
let pending = appearance_pending.clone();
|
|
Timer::single_shot(Duration::ZERO, move || {
|
|
pending.set(false);
|
|
let Some(app) = weak.upgrade() else {
|
|
return;
|
|
};
|
|
let colors = crate::message_appearance::EmailAppearance::from_app(&app);
|
|
if renderer.borrow_mut().set_appearance(colors) {
|
|
repaint_reader(&app, &renderer, gpu);
|
|
}
|
|
});
|
|
});
|
|
let weak = app.as_weak();
|
|
let r = renderer.clone();
|
|
app.on_open_email_link(move |url| {
|
|
let Some(app) = weak.upgrade() else {
|
|
return;
|
|
};
|
|
if url.starts_with('#') {
|
|
if let Some(y) = r.borrow().fragment_y(&url) {
|
|
let y = y + banner_height(&app);
|
|
app.set_email_scroll_y(-y);
|
|
app.invoke_email_scroll(y, app.get_email_viewport_height());
|
|
}
|
|
return;
|
|
}
|
|
if let Ok(parsed) = url::Url::parse(&url)
|
|
&& parsed.scheme() == "mailto"
|
|
{
|
|
app.invoke_open_compose();
|
|
// Form decoding also handles UTF-8 and percent escapes in addresses.
|
|
let address = percent_encoding::percent_decode_str(parsed.path())
|
|
.decode_utf8_lossy()
|
|
.into_owned();
|
|
app.set_compose_to(address.into());
|
|
for (key, value) in parsed.query_pairs() {
|
|
match key.as_ref() {
|
|
"subject" => app.set_compose_subject(value.into_owned().into()),
|
|
"cc" => app.set_compose_cc(value.into_owned().into()),
|
|
"bcc" => app.set_compose_bcc(value.into_owned().into()),
|
|
"body" => {
|
|
app.invoke_edit_compose_body(value.into_owned().into(), 0, 0, "".into())
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
if let Err(error) = open_email_link(&opener, &url) {
|
|
app.set_render_status(UiMessage::detail("Could not open link: {}", error));
|
|
}
|
|
});
|
|
let r = renderer.clone();
|
|
app.global::<EmailReader>()
|
|
.on_link_at(move |x, y| r.borrow().link_at(x, y).unwrap_or_default().into());
|
|
|
|
let weak = app.as_weak();
|
|
let r = renderer.clone();
|
|
let pending = Rc::new(Cell::new(false));
|
|
app.on_email_pointer_event(move |x, y, kind, control, shift, alt, meta| {
|
|
let changed = r.borrow_mut().handle_pointer_event(
|
|
x,
|
|
y,
|
|
&kind,
|
|
InputModifiers::new(control, shift, alt, meta),
|
|
);
|
|
let activation = r.borrow_mut().take_activation();
|
|
let Some(app) = weak.upgrade() else {
|
|
return;
|
|
};
|
|
if let Some(url) = activation {
|
|
app.invoke_open_email_link(url.into());
|
|
}
|
|
if kind == "up" || kind == "cancel" {
|
|
update_email_selection(&app, &r);
|
|
}
|
|
if changed && !pending.replace(true) {
|
|
let weak = app.as_weak();
|
|
let r = r.clone();
|
|
let pending = pending.clone();
|
|
Timer::single_shot(Duration::from_millis(16), move || {
|
|
pending.set(false);
|
|
if let Some(app) = weak.upgrade() {
|
|
repaint_reader(&app, &r, gpu);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
let weak = app.as_weak();
|
|
let r = renderer.clone();
|
|
let scroll_pending = Rc::new(Cell::new(false));
|
|
app.on_email_scroll(move |y, height| {
|
|
let Some(app) = weak.upgrade() else {
|
|
return;
|
|
};
|
|
let dirty = r
|
|
.borrow_mut()
|
|
.set_visible_region((y - banner_height(&app)).max(0.0), height);
|
|
if dirty {
|
|
schedule_scroll_frame(&app, &r, gpu, &scroll_pending);
|
|
}
|
|
});
|
|
let weak = app.as_weak();
|
|
let r = renderer.clone();
|
|
app.on_email_key_event(move |text, pressed, repeat, control, shift, alt, meta| {
|
|
let copied = r.borrow_mut().handle_key_event(
|
|
&text,
|
|
pressed,
|
|
repeat,
|
|
InputModifiers::new(control, shift, alt, meta),
|
|
);
|
|
if let Some(app) = weak.upgrade() {
|
|
if let Some(text) = copied {
|
|
copy_text(&app, text);
|
|
}
|
|
if r.borrow().needs_repaint() {
|
|
let (_, caret) = r.borrow().selection_carets();
|
|
if caret.valid {
|
|
let top = (-app.get_email_scroll_y() - banner_height(&app)).max(0.0);
|
|
let height = app.get_email_viewport_height();
|
|
let y = if caret.y < top {
|
|
caret.y
|
|
} else if caret.y + caret.height > top + height {
|
|
caret.y + caret.height - height
|
|
} else {
|
|
top
|
|
};
|
|
app.set_email_scroll_y(-y - banner_height(&app));
|
|
r.borrow_mut().set_visible_region(y, height);
|
|
}
|
|
repaint_reader(&app, &r, gpu);
|
|
}
|
|
}
|
|
});
|
|
let weak = app.as_weak();
|
|
let r = renderer.clone();
|
|
app.on_select_email_text(move || {
|
|
r.borrow_mut().select_all();
|
|
if let Some(app) = weak.upgrade() {
|
|
repaint_reader(&app, &r, gpu);
|
|
}
|
|
});
|
|
let weak = app.as_weak();
|
|
app.on_copy_email_status(move |status| {
|
|
if let Some(app) = weak.upgrade() {
|
|
match status.as_str() {
|
|
"Copied HTML source" => {
|
|
app.set_render_status(UiMessage::plain("Copied HTML source"))
|
|
}
|
|
"Copied selected text" => {
|
|
app.set_render_status(UiMessage::plain("Copied selected text"))
|
|
}
|
|
"Copied email text" => app.set_render_status(UiMessage::plain("Copied email text")),
|
|
"Copied link" => app.set_render_status(UiMessage::plain("Copied link")),
|
|
_ => {}
|
|
}
|
|
}
|
|
});
|
|
let weak = app.as_weak();
|
|
let r = renderer.clone();
|
|
app.global::<EmailReader>()
|
|
.on_find(move |query, direction| {
|
|
let (index, count, y) = r.borrow_mut().find(&query, direction);
|
|
if let Some(app) = weak.upgrade() {
|
|
app.global::<EmailReader>()
|
|
.set_find_status(format!("{index} / {count}").into());
|
|
if let Some(y) = y {
|
|
app.set_email_scroll_y(-y - banner_height(&app));
|
|
r.borrow_mut()
|
|
.set_visible_region(y, app.get_email_viewport_height());
|
|
}
|
|
repaint_reader(&app, &r, gpu);
|
|
}
|
|
});
|
|
let weak = app.as_weak();
|
|
let r = renderer.clone();
|
|
app.global::<EmailReader>().on_command(move |command| {
|
|
let Some(app) = weak.upgrade() else {
|
|
return;
|
|
};
|
|
let reader = app.global::<EmailReader>();
|
|
let requested_zoom = crate::preview_controls::zoom(&command, r.borrow().zoom, 0.5, 3.0);
|
|
let command = if command.starts_with("zoom-set:") {
|
|
"zoom-set"
|
|
} else {
|
|
command.as_str()
|
|
};
|
|
match command {
|
|
"viewport" => repaint_reader(&app, &r, gpu),
|
|
"zoom-set" | "zoom-in" | "zoom-out" | "zoom-reset" | "fit" => {
|
|
let old = r.borrow().zoom;
|
|
let zoom = match command {
|
|
"zoom-set" => requested_zoom,
|
|
"zoom-in" => old * 1.2,
|
|
"zoom-out" => old / 1.2,
|
|
"fit" => old / reader.get_width_ratio().max(1.0),
|
|
_ => 1.0,
|
|
}
|
|
.clamp(0.5, 3.0);
|
|
r.borrow_mut().set_auto_fit(command == "fit");
|
|
reader.set_auto_fit(command == "fit");
|
|
r.borrow_mut().set_zoom(zoom);
|
|
reader.set_zoom(zoom);
|
|
reader.set_zoom_revision(reader.get_zoom_revision().wrapping_add(1));
|
|
let banner = banner_height(&app);
|
|
let scroll = -app.get_email_scroll_y();
|
|
let scroll = if scroll < banner {
|
|
scroll
|
|
} else {
|
|
banner + (scroll - banner) * zoom / old
|
|
};
|
|
app.set_email_scroll_y(-scroll);
|
|
r.borrow_mut().set_visible_region(
|
|
(scroll - banner).max(0.0),
|
|
app.get_email_viewport_height(),
|
|
);
|
|
repaint_reader(&app, &r, gpu);
|
|
}
|
|
"clear-selection" => {
|
|
r.borrow_mut().clear_selection();
|
|
repaint_reader(&app, &r, gpu);
|
|
}
|
|
"copy-rich" => {
|
|
let renderer = r.borrow();
|
|
let text = renderer.selected_text().unwrap_or_default();
|
|
let html = renderer.selected_html();
|
|
if !crate::reader_clipboard::set_html(&html, &text) {
|
|
copy_text(&app, text);
|
|
}
|
|
}
|
|
"copy-all" => copy_text(&app, r.borrow().plain_text()),
|
|
"page-up" | "page-down" | "up" | "down" | "home" | "end" => {
|
|
let height = app.get_email_viewport_height();
|
|
let current = -app.get_email_scroll_y();
|
|
let bottom = (app.get_email_scroll_document_height() - height).max(0.0);
|
|
let y = match command {
|
|
"page-up" => current - height * 0.85,
|
|
"page-down" => current + height * 0.85,
|
|
"up" => current - 40.0,
|
|
"down" => current + 40.0,
|
|
"home" => 0.0,
|
|
_ => bottom,
|
|
}
|
|
.clamp(0.0, bottom);
|
|
app.set_email_scroll_y(-y);
|
|
r.borrow_mut()
|
|
.set_visible_region((y - banner_height(&app)).max(0.0), height);
|
|
repaint_reader(&app, &r, gpu);
|
|
}
|
|
_ => {}
|
|
}
|
|
});
|
|
}
|
|
fn copy_text(app: &AppWindow, text: String) {
|
|
app.set_clipboard_request(text.into());
|
|
app.set_clipboard_request_id(app.get_clipboard_request_id().wrapping_add(1));
|
|
}
|