785 lines
30 KiB
Rust
785 lines
30 KiB
Rust
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
use alacritty_terminal::{
|
|
index::{Column as TermColumn, Point as TermPoint, Side as TermSide},
|
|
selection::{Selection, SelectionType},
|
|
term::TermMode,
|
|
};
|
|
use cosmic::{
|
|
iced::{
|
|
advanced::graphics::text::{font_system, Raw},
|
|
event::{Event, Status},
|
|
keyboard::{Event as KeyEvent, KeyCode, Modifiers},
|
|
mouse::{self, Button, Event as MouseEvent, ScrollDelta},
|
|
Color, Element, Length, Padding, Point, Rectangle, Size, Vector,
|
|
},
|
|
iced_core::{
|
|
clipboard::Clipboard,
|
|
image,
|
|
layout::{self, Layout},
|
|
renderer::{self, Quad},
|
|
text,
|
|
widget::{self, tree, Widget},
|
|
Shell,
|
|
},
|
|
};
|
|
use cosmic_text::LayoutGlyph;
|
|
use std::{
|
|
cell::Cell,
|
|
cmp,
|
|
sync::Mutex,
|
|
time::{Duration, Instant},
|
|
};
|
|
|
|
use crate::{Terminal, TerminalScroll};
|
|
|
|
pub struct TerminalBox<'a, Message> {
|
|
terminal: &'a Mutex<Terminal>,
|
|
padding: Padding,
|
|
click_timing: Duration,
|
|
context_menu: Option<Point>,
|
|
on_context_menu: Option<Box<dyn Fn(Option<Point>) -> Message + 'a>>,
|
|
}
|
|
|
|
impl<'a, Message> TerminalBox<'a, Message>
|
|
where
|
|
Message: Clone,
|
|
{
|
|
pub fn new(terminal: &'a Mutex<Terminal>) -> Self {
|
|
Self {
|
|
terminal,
|
|
padding: Padding::new(0.0),
|
|
click_timing: Duration::from_millis(500),
|
|
context_menu: None,
|
|
on_context_menu: None,
|
|
}
|
|
}
|
|
|
|
pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
|
|
self.padding = padding.into();
|
|
self
|
|
}
|
|
|
|
pub fn click_timing(mut self, click_timing: Duration) -> Self {
|
|
self.click_timing = click_timing;
|
|
self
|
|
}
|
|
|
|
pub fn context_menu(mut self, position: Point) -> Self {
|
|
self.context_menu = Some(position);
|
|
self
|
|
}
|
|
|
|
pub fn on_context_menu(
|
|
mut self,
|
|
on_context_menu: impl Fn(Option<Point>) -> Message + 'a,
|
|
) -> Self {
|
|
self.on_context_menu = Some(Box::new(on_context_menu));
|
|
self
|
|
}
|
|
}
|
|
|
|
pub fn terminal_box<'a, Message>(terminal: &'a Mutex<Terminal>) -> TerminalBox<'a, Message>
|
|
where
|
|
Message: Clone,
|
|
{
|
|
TerminalBox::new(terminal)
|
|
}
|
|
|
|
impl<'a, Message, Renderer> Widget<Message, Renderer> for TerminalBox<'a, Message>
|
|
where
|
|
Message: Clone,
|
|
Renderer:
|
|
renderer::Renderer + image::Renderer<Handle = image::Handle> + text::Renderer<Raw = Raw>,
|
|
{
|
|
fn tag(&self) -> tree::Tag {
|
|
tree::Tag::of::<State>()
|
|
}
|
|
|
|
fn state(&self) -> tree::State {
|
|
tree::State::new(State::new())
|
|
}
|
|
|
|
fn width(&self) -> Length {
|
|
Length::Fill
|
|
}
|
|
|
|
fn height(&self) -> Length {
|
|
Length::Fill
|
|
}
|
|
|
|
fn layout(
|
|
&self,
|
|
_tree: &mut widget::Tree,
|
|
_renderer: &Renderer,
|
|
limits: &layout::Limits,
|
|
) -> layout::Node {
|
|
let limits = limits.width(Length::Fill).height(Length::Fill);
|
|
|
|
let mut terminal = self.terminal.lock().unwrap();
|
|
//TODO: set size?
|
|
terminal.with_buffer_mut(|buffer| {
|
|
let mut font_system = font_system().write().unwrap();
|
|
buffer.shape_until_scroll(font_system.raw(), true);
|
|
});
|
|
|
|
terminal.with_buffer(|buffer| {
|
|
let mut layout_lines = 0;
|
|
for line in buffer.lines.iter() {
|
|
match line.layout_opt() {
|
|
Some(layout) => layout_lines += layout.len(),
|
|
None => (),
|
|
}
|
|
}
|
|
|
|
let height = layout_lines as f32 * buffer.metrics().line_height;
|
|
let size = Size::new(limits.max().width, height);
|
|
|
|
layout::Node::new(limits.resolve(size))
|
|
})
|
|
}
|
|
|
|
fn mouse_interaction(
|
|
&self,
|
|
tree: &widget::Tree,
|
|
layout: Layout<'_>,
|
|
cursor_position: mouse::Cursor,
|
|
_viewport: &Rectangle,
|
|
_renderer: &Renderer,
|
|
) -> mouse::Interaction {
|
|
let state = tree.state.downcast_ref::<State>();
|
|
|
|
match &state.dragging {
|
|
Some(Dragging::Scrollbar { .. }) => return mouse::Interaction::Idle,
|
|
_ => {}
|
|
}
|
|
|
|
if let Some(p) = cursor_position.position_in(layout.bounds()) {
|
|
let terminal = self.terminal.lock().unwrap();
|
|
let buffer_size = terminal.with_buffer(|buffer| buffer.size());
|
|
|
|
let x = p.x - self.padding.left;
|
|
let y = p.y - self.padding.top;
|
|
if x >= 0.0 && x < buffer_size.0 && y >= 0.0 && y < buffer_size.1 {
|
|
return mouse::Interaction::Text;
|
|
}
|
|
}
|
|
|
|
mouse::Interaction::Idle
|
|
}
|
|
|
|
fn draw(
|
|
&self,
|
|
tree: &widget::Tree,
|
|
renderer: &mut Renderer,
|
|
_theme: &Renderer::Theme,
|
|
_style: &renderer::Style,
|
|
layout: Layout<'_>,
|
|
_cursor_position: mouse::Cursor,
|
|
viewport: &Rectangle,
|
|
) {
|
|
let instant = Instant::now();
|
|
|
|
let state = tree.state.downcast_ref::<State>();
|
|
|
|
let mut terminal = self.terminal.lock().unwrap();
|
|
|
|
//TODO: make this configurable
|
|
let scrollbar_w = 8.0;
|
|
|
|
let view_position =
|
|
layout.position() + [self.padding.left as f32, self.padding.top as f32].into();
|
|
let view_w = cmp::min(viewport.width as i32, layout.bounds().width as i32)
|
|
- self.padding.horizontal() as i32
|
|
- scrollbar_w as i32;
|
|
let view_h = cmp::min(viewport.height as i32, layout.bounds().height as i32)
|
|
- self.padding.vertical() as i32;
|
|
|
|
if view_w <= 0 || view_h <= 0 {
|
|
// Zero sized image
|
|
return;
|
|
}
|
|
|
|
// Ensure terminal is the right size
|
|
terminal.resize(view_w as u32, view_h as u32);
|
|
|
|
// Ensure terminal is shaped
|
|
terminal.with_buffer_mut(|buffer| {
|
|
let mut font_system = font_system().write().unwrap();
|
|
buffer.shape_until_scroll(font_system.raw(), true);
|
|
});
|
|
|
|
// Render default background
|
|
{
|
|
let background_color = cosmic_text::Color(terminal.default_attrs().metadata as u32);
|
|
renderer.fill_quad(
|
|
Quad {
|
|
bounds: Rectangle::new(
|
|
view_position,
|
|
Size::new(view_w as f32 + scrollbar_w, view_h as f32),
|
|
),
|
|
border_radius: 0.0.into(),
|
|
border_width: 0.0,
|
|
border_color: Color::TRANSPARENT,
|
|
},
|
|
Color::new(
|
|
background_color.r() as f32 / 255.0,
|
|
background_color.g() as f32 / 255.0,
|
|
background_color.b() as f32 / 255.0,
|
|
background_color.a() as f32 / 255.0,
|
|
),
|
|
);
|
|
}
|
|
|
|
// Render cell backgrounds that do not match default
|
|
terminal.with_buffer(|buffer| {
|
|
for run in buffer.layout_runs() {
|
|
struct BgRect {
|
|
default_metadata: usize,
|
|
metadata: usize,
|
|
start_x: f32,
|
|
end_x: f32,
|
|
line_height: f32,
|
|
line_top: f32,
|
|
view_position: Point,
|
|
}
|
|
|
|
impl BgRect {
|
|
fn update<Renderer: renderer::Renderer>(
|
|
&mut self,
|
|
glyph: &LayoutGlyph,
|
|
renderer: &mut Renderer,
|
|
) {
|
|
if glyph.metadata == self.metadata {
|
|
self.end_x = glyph.x + glyph.w;
|
|
} else {
|
|
self.fill(renderer);
|
|
self.metadata = glyph.metadata;
|
|
self.start_x = glyph.x;
|
|
self.end_x = glyph.x + glyph.w;
|
|
}
|
|
}
|
|
|
|
fn fill<Renderer: renderer::Renderer>(&mut self, renderer: &mut Renderer) {
|
|
if self.metadata == self.default_metadata {
|
|
return;
|
|
}
|
|
|
|
let color = cosmic_text::Color(self.metadata as u32);
|
|
renderer.fill_quad(
|
|
Quad {
|
|
bounds: Rectangle::new(
|
|
self.view_position + Vector::new(self.start_x, self.line_top),
|
|
Size::new(self.end_x - self.start_x, self.line_height),
|
|
),
|
|
border_radius: 0.0.into(),
|
|
border_width: 0.0,
|
|
border_color: Color::TRANSPARENT,
|
|
},
|
|
Color::new(
|
|
color.r() as f32 / 255.0,
|
|
color.g() as f32 / 255.0,
|
|
color.b() as f32 / 255.0,
|
|
color.a() as f32 / 255.0,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
let default_metadata = terminal.default_attrs().metadata;
|
|
let mut bg_rect = BgRect {
|
|
default_metadata,
|
|
metadata: default_metadata,
|
|
start_x: 0.0,
|
|
end_x: 0.0,
|
|
line_height: buffer.metrics().line_height,
|
|
line_top: run.line_top,
|
|
view_position,
|
|
};
|
|
for glyph in run.glyphs.iter() {
|
|
bg_rect.update(glyph, renderer);
|
|
}
|
|
bg_rect.fill(renderer);
|
|
}
|
|
});
|
|
|
|
renderer.fill_raw(Raw {
|
|
buffer: terminal.buffer_weak(),
|
|
position: view_position,
|
|
color: Color::new(1.0, 1.0, 1.0, 1.0), // TODO
|
|
clip_bounds: Rectangle::new(view_position, Size::new(view_w as f32, view_h as f32)),
|
|
});
|
|
|
|
// Draw scrollbar
|
|
if let Some((start, end)) = terminal.scrollbar() {
|
|
let scrollbar_y = start * view_h as f32;
|
|
let scrollbar_h = end * view_h as f32 - scrollbar_y;
|
|
let scrollbar_rect = Rectangle::new(
|
|
[view_w as f32, scrollbar_y].into(),
|
|
Size::new(scrollbar_w, scrollbar_h),
|
|
);
|
|
let scrollbar_alpha = match &state.dragging {
|
|
Some(Dragging::Scrollbar { .. }) => 0.5,
|
|
_ => 0.25,
|
|
};
|
|
renderer.fill_quad(
|
|
Quad {
|
|
bounds: scrollbar_rect + Vector::new(view_position.x, view_position.y),
|
|
border_radius: 0.0.into(),
|
|
border_width: 0.0,
|
|
border_color: Color::TRANSPARENT,
|
|
},
|
|
Color::new(1.0, 1.0, 1.0, scrollbar_alpha),
|
|
);
|
|
state.scrollbar_rect.set(scrollbar_rect);
|
|
} else {
|
|
state.scrollbar_rect.set(Rectangle::default())
|
|
}
|
|
|
|
let duration = instant.elapsed();
|
|
log::debug!("redraw {}, {}: {:?}", view_w, view_h, duration);
|
|
}
|
|
|
|
fn on_event(
|
|
&mut self,
|
|
tree: &mut widget::Tree,
|
|
event: Event,
|
|
layout: Layout<'_>,
|
|
cursor_position: mouse::Cursor,
|
|
_renderer: &Renderer,
|
|
_clipboard: &mut dyn Clipboard,
|
|
shell: &mut Shell<'_, Message>,
|
|
_viewport: &Rectangle<f32>,
|
|
) -> Status {
|
|
let state = tree.state.downcast_mut::<State>();
|
|
let scrollbar_rect = state.scrollbar_rect.get();
|
|
let mut terminal = self.terminal.lock().unwrap();
|
|
let buffer_size = terminal.with_buffer(|buffer| buffer.size());
|
|
|
|
let is_app_cursor = terminal.term.lock().mode().contains(TermMode::APP_CURSOR);
|
|
|
|
let mut status = Status::Ignored;
|
|
match event {
|
|
Event::Keyboard(KeyEvent::KeyPressed {
|
|
key_code,
|
|
modifiers,
|
|
}) => match (
|
|
modifiers.logo(),
|
|
modifiers.control(),
|
|
modifiers.alt(),
|
|
modifiers.shift(),
|
|
) {
|
|
(true, _, _, _) => {
|
|
// Ignore super keys
|
|
}
|
|
(_, true, _, _) => {
|
|
// Ignore ctrl keys
|
|
}
|
|
(_, _, true, _) => {
|
|
// Ignore alt keys
|
|
//TODO: alt keys for control characters
|
|
}
|
|
// Handle shift keys
|
|
(_, _, _, true) => match key_code {
|
|
KeyCode::End => {
|
|
terminal.scroll(TerminalScroll::Bottom);
|
|
}
|
|
KeyCode::Home => {
|
|
terminal.scroll(TerminalScroll::Top);
|
|
}
|
|
KeyCode::PageDown => {
|
|
terminal.scroll(TerminalScroll::PageDown);
|
|
}
|
|
KeyCode::PageUp => {
|
|
terminal.scroll(TerminalScroll::PageUp);
|
|
}
|
|
KeyCode::Tab => {
|
|
terminal.input_scroll(b"\x1B[Z".as_slice());
|
|
}
|
|
_ => {}
|
|
},
|
|
// Handle keys with no modifiers
|
|
(_, _, _, false) => match key_code {
|
|
KeyCode::Backspace => {
|
|
terminal.input_scroll(b"\x7F".as_slice());
|
|
status = Status::Captured;
|
|
}
|
|
KeyCode::Tab => {
|
|
terminal.input_scroll(b"\t".as_slice());
|
|
status = Status::Captured;
|
|
}
|
|
KeyCode::Enter => {
|
|
terminal.input_scroll(b"\r".as_slice());
|
|
status = Status::Captured;
|
|
}
|
|
KeyCode::Escape => {
|
|
terminal.input_scroll(b"\x1B".as_slice());
|
|
status = Status::Captured;
|
|
}
|
|
KeyCode::Up => {
|
|
let code = if is_app_cursor { b"\x1BOA" } else { b"\x1B[A" };
|
|
|
|
terminal.input_scroll(code.as_slice());
|
|
status = Status::Captured;
|
|
}
|
|
KeyCode::Down => {
|
|
let code = if is_app_cursor { b"\x1BOB" } else { b"\x1B[B" };
|
|
|
|
terminal.input_scroll(code.as_slice());
|
|
status = Status::Captured;
|
|
}
|
|
KeyCode::Right => {
|
|
let code = if is_app_cursor { b"\x1BOC" } else { b"\x1B[C" };
|
|
|
|
terminal.input_scroll(code.as_slice());
|
|
status = Status::Captured;
|
|
}
|
|
KeyCode::Left => {
|
|
let code = if is_app_cursor { b"\x1BOD" } else { b"\x1B[D" };
|
|
|
|
terminal.input_scroll(code.as_slice());
|
|
status = Status::Captured;
|
|
}
|
|
KeyCode::End => {
|
|
let code = if is_app_cursor { b"\x1BOF" } else { b"\x1B[F" };
|
|
|
|
terminal.input_scroll(code.as_slice());
|
|
status = Status::Captured;
|
|
}
|
|
KeyCode::Home => {
|
|
let code = if is_app_cursor { b"\x1BOH" } else { b"\x1B[H" };
|
|
|
|
terminal.input_scroll(code.as_slice());
|
|
status = Status::Captured;
|
|
}
|
|
KeyCode::Insert => {
|
|
terminal.input_scroll(b"\x1B[2~".as_slice());
|
|
status = Status::Captured;
|
|
}
|
|
KeyCode::Delete => {
|
|
terminal.input_scroll(b"\x1B[3~".as_slice());
|
|
status = Status::Captured;
|
|
}
|
|
KeyCode::PageUp => {
|
|
terminal.input_scroll(b"\x1B[5~".as_slice());
|
|
status = Status::Captured;
|
|
}
|
|
KeyCode::PageDown => {
|
|
terminal.input_scroll(b"\x1B[6~".as_slice());
|
|
status = Status::Captured;
|
|
}
|
|
KeyCode::F1 => {
|
|
terminal.input_scroll(b"\x1BOP".as_slice());
|
|
status = Status::Captured;
|
|
}
|
|
KeyCode::F2 => {
|
|
terminal.input_scroll(b"\x1BOQ".as_slice());
|
|
status = Status::Captured;
|
|
}
|
|
KeyCode::F3 => {
|
|
terminal.input_scroll(b"\x1BOR".as_slice());
|
|
status = Status::Captured;
|
|
}
|
|
KeyCode::F4 => {
|
|
terminal.input_scroll(b"\x1BOS".as_slice());
|
|
status = Status::Captured;
|
|
}
|
|
KeyCode::F5 => {
|
|
terminal.input_scroll(b"\x1B[15~".as_slice());
|
|
status = Status::Captured;
|
|
}
|
|
KeyCode::F6 => {
|
|
terminal.input_scroll(b"\x1B[17~".as_slice());
|
|
status = Status::Captured;
|
|
}
|
|
KeyCode::F7 => {
|
|
terminal.input_scroll(b"\x1B[18~".as_slice());
|
|
status = Status::Captured;
|
|
}
|
|
KeyCode::F8 => {
|
|
terminal.input_scroll(b"\x1B[19~".as_slice());
|
|
status = Status::Captured;
|
|
}
|
|
KeyCode::F9 => {
|
|
terminal.input_scroll(b"\x1B[20~".as_slice());
|
|
status = Status::Captured;
|
|
}
|
|
KeyCode::F10 => {
|
|
terminal.input_scroll(b"\x1B[21~".as_slice());
|
|
status = Status::Captured;
|
|
}
|
|
KeyCode::F11 => {
|
|
terminal.input_scroll(b"\x1B[23~".as_slice());
|
|
status = Status::Captured;
|
|
}
|
|
KeyCode::F12 => {
|
|
terminal.input_scroll(b"\x1B[24~".as_slice());
|
|
status = Status::Captured;
|
|
}
|
|
_ => (),
|
|
},
|
|
},
|
|
Event::Keyboard(KeyEvent::ModifiersChanged(modifiers)) => {
|
|
state.modifiers = modifiers;
|
|
}
|
|
Event::Keyboard(KeyEvent::CharacterReceived(character)) => {
|
|
match (
|
|
state.modifiers.logo(),
|
|
state.modifiers.control(),
|
|
state.modifiers.alt(),
|
|
state.modifiers.shift(),
|
|
) {
|
|
(true, _, _, _) => {
|
|
// Ignore super
|
|
}
|
|
(false, true, _, false) => {
|
|
// Handle ctrl for control characters (Ctrl-A to Ctrl-Z)
|
|
if character.is_control() {
|
|
let mut buf = [0, 0, 0, 0];
|
|
let str = character.encode_utf8(&mut buf);
|
|
terminal.input_scroll(str.as_bytes().to_vec());
|
|
status = Status::Captured;
|
|
}
|
|
}
|
|
(false, true, _, true) => {
|
|
// Ignore ctrl+shift
|
|
}
|
|
(false, false, true, _) => {
|
|
if !character.is_control() {
|
|
// Handle alt for non-control characters
|
|
let mut buf = [0x1B, 0, 0, 0, 0];
|
|
let len = {
|
|
let str = character.encode_utf8(&mut buf[1..]);
|
|
str.len() + 1
|
|
};
|
|
terminal.input_scroll(buf[..len].to_vec());
|
|
status = Status::Captured;
|
|
}
|
|
}
|
|
(false, false, false, _) => {
|
|
// Handle no modifiers for non-control characters
|
|
if !character.is_control() {
|
|
let mut buf = [0, 0, 0, 0];
|
|
let str = character.encode_utf8(&mut buf);
|
|
terminal.input_scroll(str.as_bytes().to_vec());
|
|
status = Status::Captured;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Event::Mouse(MouseEvent::ButtonPressed(button)) => {
|
|
if let Some(p) = cursor_position.position_in(layout.bounds()) {
|
|
// Handle left click drag
|
|
if let Button::Left = button {
|
|
let x = p.x - self.padding.left;
|
|
let y = p.y - self.padding.top;
|
|
if x >= 0.0 && x < buffer_size.0 && y >= 0.0 && y < buffer_size.1 {
|
|
let click_kind =
|
|
if let Some((click_kind, click_time)) = state.click.take() {
|
|
if click_time.elapsed() < self.click_timing {
|
|
match click_kind {
|
|
ClickKind::Single => ClickKind::Double,
|
|
ClickKind::Double => ClickKind::Triple,
|
|
ClickKind::Triple => ClickKind::Single,
|
|
}
|
|
} else {
|
|
ClickKind::Single
|
|
}
|
|
} else {
|
|
ClickKind::Single
|
|
};
|
|
//TODO: better calculation of position
|
|
let col = x / terminal.size().cell_width;
|
|
let row = y / terminal.size().cell_height;
|
|
let location = terminal.viewport_to_point(TermPoint::new(
|
|
row as usize,
|
|
TermColumn(col as usize),
|
|
));
|
|
let side = if col.fract() < 0.5 {
|
|
TermSide::Left
|
|
} else {
|
|
TermSide::Right
|
|
};
|
|
let selection = match click_kind {
|
|
ClickKind::Single => {
|
|
Selection::new(SelectionType::Simple, location, side)
|
|
}
|
|
ClickKind::Double => {
|
|
Selection::new(SelectionType::Semantic, location, side)
|
|
}
|
|
ClickKind::Triple => {
|
|
Selection::new(SelectionType::Lines, location, side)
|
|
}
|
|
};
|
|
{
|
|
let mut term = terminal.term.lock();
|
|
term.selection = Some(selection);
|
|
}
|
|
terminal.update();
|
|
state.click = Some((click_kind, Instant::now()));
|
|
state.dragging = Some(Dragging::Buffer);
|
|
} else if scrollbar_rect.contains(Point::new(x, y)) {
|
|
if let Some(start_scroll) = terminal.scrollbar() {
|
|
state.dragging = Some(Dragging::Scrollbar {
|
|
start_y: y,
|
|
start_scroll,
|
|
});
|
|
}
|
|
} else if x >= scrollbar_rect.x
|
|
&& x < (scrollbar_rect.x + scrollbar_rect.width)
|
|
{
|
|
if let Some(start_scroll) = terminal.scrollbar() {
|
|
let scroll_ratio =
|
|
terminal.with_buffer(|buffer| y / buffer.size().1);
|
|
terminal.scroll_to(scroll_ratio);
|
|
state.dragging = Some(Dragging::Scrollbar {
|
|
start_y: y,
|
|
start_scroll,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update context menu state
|
|
if let Some(on_context_menu) = &self.on_context_menu {
|
|
shell.publish((on_context_menu)(match self.context_menu {
|
|
Some(_) => None,
|
|
None => match button {
|
|
Button::Right => Some(p),
|
|
_ => None,
|
|
},
|
|
}));
|
|
}
|
|
|
|
status = Status::Captured;
|
|
}
|
|
}
|
|
Event::Mouse(MouseEvent::ButtonReleased(Button::Left)) => {
|
|
state.dragging = None;
|
|
status = Status::Captured;
|
|
}
|
|
Event::Mouse(MouseEvent::CursorMoved { .. }) => {
|
|
if let Some(dragging) = &state.dragging {
|
|
if let Some(p) = cursor_position.position() {
|
|
let x = (p.x - layout.bounds().x) - self.padding.left;
|
|
let y = (p.y - layout.bounds().y) - self.padding.top;
|
|
match dragging {
|
|
Dragging::Buffer => {
|
|
//TODO: better calculation of position
|
|
let col = x / terminal.size().cell_width;
|
|
let row = y / terminal.size().cell_height;
|
|
let location = terminal.viewport_to_point(TermPoint::new(
|
|
row as usize,
|
|
TermColumn(col as usize),
|
|
));
|
|
let side = if col.fract() < 0.5 {
|
|
TermSide::Left
|
|
} else {
|
|
TermSide::Right
|
|
};
|
|
{
|
|
let mut term = terminal.term.lock();
|
|
if let Some(selection) = &mut term.selection {
|
|
selection.update(location, side);
|
|
}
|
|
}
|
|
terminal.update();
|
|
}
|
|
Dragging::Scrollbar {
|
|
start_y,
|
|
start_scroll,
|
|
} => {
|
|
let scroll_offset = terminal
|
|
.with_buffer(|buffer| ((y - start_y) / buffer.size().1));
|
|
terminal.scroll_to(start_scroll.0 + scroll_offset);
|
|
}
|
|
}
|
|
}
|
|
status = Status::Captured;
|
|
}
|
|
}
|
|
Event::Mouse(MouseEvent::WheelScrolled { delta }) => {
|
|
if let Some(_p) = cursor_position.position_in(layout.bounds()) {
|
|
match delta {
|
|
ScrollDelta::Lines { x, y } => {
|
|
//TODO: this adjustment is just a guess!
|
|
state.scroll_pixels = 0.0;
|
|
let lines = (-y * 6.0) as i32;
|
|
if lines != 0 {
|
|
terminal.scroll(TerminalScroll::Delta(-lines));
|
|
}
|
|
status = Status::Captured;
|
|
}
|
|
ScrollDelta::Pixels { x, y } => {
|
|
//TODO: this adjustment is just a guess!
|
|
state.scroll_pixels -= y * 6.0;
|
|
let mut lines = 0;
|
|
let metrics = terminal.with_buffer(|buffer| buffer.metrics());
|
|
while state.scroll_pixels <= -metrics.line_height {
|
|
lines -= 1;
|
|
state.scroll_pixels += metrics.line_height;
|
|
}
|
|
while state.scroll_pixels >= metrics.line_height {
|
|
lines += 1;
|
|
state.scroll_pixels -= metrics.line_height;
|
|
}
|
|
if lines != 0 {
|
|
terminal.scroll(TerminalScroll::Delta(-lines));
|
|
}
|
|
status = Status::Captured;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
_ => (),
|
|
}
|
|
|
|
status
|
|
}
|
|
}
|
|
|
|
impl<'a, Message, Renderer> From<TerminalBox<'a, Message>> for Element<'a, Message, Renderer>
|
|
where
|
|
Message: Clone + 'a,
|
|
Renderer:
|
|
renderer::Renderer + image::Renderer<Handle = image::Handle> + text::Renderer<Raw = Raw>,
|
|
{
|
|
fn from(terminal_box: TerminalBox<'a, Message>) -> Self {
|
|
Self::new(terminal_box)
|
|
}
|
|
}
|
|
|
|
enum ClickKind {
|
|
Single,
|
|
Double,
|
|
Triple,
|
|
}
|
|
|
|
enum Dragging {
|
|
Buffer,
|
|
Scrollbar {
|
|
start_y: f32,
|
|
start_scroll: (f32, f32),
|
|
},
|
|
}
|
|
|
|
pub struct State {
|
|
modifiers: Modifiers,
|
|
click: Option<(ClickKind, Instant)>,
|
|
dragging: Option<Dragging>,
|
|
scroll_pixels: f32,
|
|
scrollbar_rect: Cell<Rectangle<f32>>,
|
|
}
|
|
|
|
impl State {
|
|
/// Creates a new [`State`].
|
|
pub fn new() -> State {
|
|
State {
|
|
modifiers: Modifiers::empty(),
|
|
click: None,
|
|
dragging: None,
|
|
scroll_pixels: 0.0,
|
|
scrollbar_rect: Cell::new(Rectangle::default()),
|
|
}
|
|
}
|
|
}
|