use freya::{
prelude::*,
radio::use_radio,
};
use freya_terminal::prelude::*;
use keyboard_types::Modifiers;
use crate::state::{
AppChannel,
AppState,
};
#[derive(PartialEq)]
pub struct TerminalPane {
pub handle: TerminalHandle,
pub on_reopen: Callback<(), ()>,
pub is_focused: bool,
pub focus_id: AccessibilityId,
pub instance_id: usize,
pub terminal_index: usize,
}
impl Component for TerminalPane {
fn render(&self) -> impl IntoElement {
let focus = Focus::new_for_id(self.focus_id);
let instance_id = self.instance_id;
let mut radio = use_radio::<AppState, AppChannel>(AppChannel::Instance(instance_id));
let mut exited = use_state(|| false);
let on_reopen = self.on_reopen.clone();
let handle = self.handle.clone();
let handle_for_future = handle.clone();
let handle_for_terminal = handle.clone();
let handle_for_mouse = handle.clone();
use_future(move || {
let handle = handle_for_future.clone();
async move {
handle.closed().await;
*exited.write() = true;
}
});
let mut dimensions = use_state(|| (0.0, 0.0));
let font_size = radio.read().font_size;
let on_secondary_press = move |_: Event<PressEventData>| {
let on_reopen = on_reopen.clone();
ContextMenu::open(
Menu::new().child(
MenuButton::new()
.on_press(move |_| {
ContextMenu::close();
on_reopen.call(());
})
.child("Reopen"),
),
);
};
rect()
.expanded()
.padding(6.)
.background(if self.is_focused {
(30, 30, 30)
} else {
(10, 10, 10)
})
.a11y_id(focus.a11y_id())
.a11y_auto_focus(self.is_focused)
.on_mouse_down(move |_| {
focus.request_focus();
if let Some(instance) = radio
.write()
.instances
.iter_mut()
.find(|i| i.id == instance_id)
{
instance.focused_terminal_id = Some(focus.a11y_id());
}
})
.on_secondary_press(on_secondary_press)
.on_key_down(move |e: Event<KeyboardEventData>| {
let mods = e.modifiers;
let ctrl_shift = mods.contains(Modifiers::CONTROL | Modifiers::SHIFT);
let ctrl = mods.contains(Modifiers::CONTROL);
match &e.key {
Key::Character(ch) if ctrl_shift && ch.eq_ignore_ascii_case("c") => {
if let Some(text) = handle.get_selected_text() {
let _ = Clipboard::set(text);
}
}
Key::Character(ch) if ctrl_shift && ch.eq_ignore_ascii_case("v") => {
if let Ok(text) = Clipboard::get() {
let _ = handle.write(text.as_bytes());
}
}
Key::Character(ch) if ctrl && (ch == "+" || ch == "=") => {
radio.write_channel(AppChannel::Instances).font_size =
(font_size + 1.0).min(48.0);
}
Key::Character(ch) if ctrl && ch == "-" => {
radio.write_channel(AppChannel::Instances).font_size =
(font_size - 1.0).max(8.0);
}
Key::Character(ch) if ctrl && ch.len() == 1 => {
let _ = handle.write(&[ch.as_bytes()[0] & 0x1f]);
}
Key::Named(NamedKey::ArrowUp)
| Key::Named(NamedKey::ArrowRight)
| Key::Named(NamedKey::ArrowDown)
| Key::Named(NamedKey::ArrowUp)
if !mods.is_empty() => {}
Key::Named(NamedKey::Enter) => {
let _ = handle.write(b"\r");
}
Key::Named(NamedKey::Backspace) => {
let _ = handle.write(&[0x7f]);
}
Key::Named(NamedKey::Delete) => {
let _ = handle.write(b"\x1b[3~");
}
Key::Named(NamedKey::Tab) if !ctrl => {
let _ = handle.write(b"\t");
e.stop_propagation();
e.prevent_default();
}
Key::Named(NamedKey::Escape) => {
let _ = handle.write(&[0x1b]);
}
Key::Named(NamedKey::ArrowUp) => {
let _ = handle.write(b"\x1b[A");
}
Key::Named(NamedKey::ArrowDown) => {
let _ = handle.write(b"\x1b[B");
}
Key::Named(NamedKey::ArrowLeft) => {
let _ = handle.write(b"\x1b[D");
}
Key::Named(NamedKey::ArrowRight) => {
let _ = handle.write(b"\x1b[C");
}
_ => {
if let Some(ch) = e.try_as_str() {
let _ = handle.write(ch.as_bytes());
}
}
}
})
.child(if *exited.read() {
"Terminal exited".into_element()
} else {
Terminal::new(handle_for_terminal)
.font_size(font_size)
.on_measured(move |(char_width, line_height)| {
dimensions.set((char_width, line_height));
})
.on_mouse_down({
let handle = handle_for_mouse.clone();
move |e: Event<MouseEventData>| {
focus.request_focus();
let (char_width, line_height) = dimensions();
let col = (e.element_location.x / char_width as f64).floor() as usize;
let row = (e.element_location.y / line_height as f64).floor() as usize;
handle.start_selection(row, col);
}
})
.on_mouse_move({
let handle = handle_for_mouse.clone();
move |e: Event<MouseEventData>| {
let (char_width, line_height) = dimensions();
let col = (e.element_location.x / char_width as f64).floor() as usize;
let row = (e.element_location.y / line_height as f64).floor() as usize;
handle.update_selection(row, col);
}
})
.on_mouse_up({
let handle = handle_for_mouse.clone();
move |_| {
handle.end_selection();
}
})
.on_wheel({
let handle = handle_for_mouse.clone();
move |e: Event<WheelEventData>| {
let delta = if e.delta_y < 0.0 { -3 } else { 3 };
handle.scroll(delta);
}
})
.into_element()
})
}
}