Implement drag scroll, fixes #154
This commit is contained in:
parent
f4ef25e755
commit
04f2812f4e
3 changed files with 109 additions and 53 deletions
32
src/main.rs
32
src/main.rs
|
|
@ -9,6 +9,7 @@ use cosmic::{
|
|||
cosmic_theme, executor,
|
||||
font::Font,
|
||||
iced::{
|
||||
self,
|
||||
advanced::graphics::text::font_system,
|
||||
clipboard, event,
|
||||
futures::{self, SinkExt},
|
||||
|
|
@ -318,6 +319,7 @@ enum NewTab {
|
|||
#[derive(Clone, Debug)]
|
||||
pub enum Message {
|
||||
AppTheme(AppTheme),
|
||||
AutoScroll(Option<f32>),
|
||||
Config(Config),
|
||||
ConfigState(ConfigState),
|
||||
CloseFile,
|
||||
|
|
@ -374,6 +376,7 @@ pub enum Message {
|
|||
SaveAll,
|
||||
SaveAsDialog(Option<segmented_button::Entity>),
|
||||
SaveAsResult(segmented_button::Entity, DialogResult),
|
||||
Scroll(f32),
|
||||
SelectAll,
|
||||
SystemThemeModeChange(cosmic_theme::ThemeMode),
|
||||
SyntaxTheme(usize, bool),
|
||||
|
|
@ -438,6 +441,7 @@ pub struct App {
|
|||
theme_names: Vec<String>,
|
||||
context_page: ContextPage,
|
||||
text_box_id: widget::Id,
|
||||
auto_scroll: Option<f32>,
|
||||
dialog_opt: Option<Dialog<Message>>,
|
||||
dialog_page_opt: Option<DialogPage>,
|
||||
find_opt: Option<bool>,
|
||||
|
|
@ -1323,6 +1327,7 @@ impl Application for App {
|
|||
theme_names,
|
||||
context_page: ContextPage::Settings,
|
||||
text_box_id: widget::Id::unique(),
|
||||
auto_scroll: None,
|
||||
dialog_opt: None,
|
||||
dialog_page_opt: None,
|
||||
find_opt: None,
|
||||
|
|
@ -1566,6 +1571,9 @@ impl Application for App {
|
|||
self.config.app_theme = app_theme;
|
||||
return self.save_config();
|
||||
}
|
||||
Message::AutoScroll(auto_scroll) => {
|
||||
self.auto_scroll = auto_scroll;
|
||||
}
|
||||
Message::Config(config) => {
|
||||
if config != self.config {
|
||||
log::info!("update config");
|
||||
|
|
@ -2330,6 +2338,16 @@ impl Application for App {
|
|||
editor.set_selection(selection);
|
||||
}
|
||||
}
|
||||
Message::Scroll(auto_scroll) => {
|
||||
if let Some(Tab::Editor(tab)) = self.active_tab_mut() {
|
||||
let mut editor = tab.editor.lock().unwrap();
|
||||
editor.with_buffer_mut(|buffer| {
|
||||
let mut scroll = buffer.scroll();
|
||||
scroll.vertical += auto_scroll;
|
||||
buffer.set_scroll(scroll);
|
||||
});
|
||||
}
|
||||
}
|
||||
Message::SystemThemeModeChange(_theme_mode) => {
|
||||
return self.update_config();
|
||||
}
|
||||
|
|
@ -2687,6 +2705,7 @@ impl Application for App {
|
|||
Some(Tab::Editor(tab)) => {
|
||||
let mut text_box = text_box(&tab.editor, self.config.metrics())
|
||||
.id(self.text_box_id.clone())
|
||||
.on_auto_scroll(Message::AutoScroll)
|
||||
.on_changed(Message::TabChanged(tab_id))
|
||||
.has_context_menu(tab.context_menu.is_some())
|
||||
.on_context_menu(move |position_opt| {
|
||||
|
|
@ -2928,7 +2947,7 @@ impl Application for App {
|
|||
struct ConfigStateSubscription;
|
||||
struct ThemeSubscription;
|
||||
|
||||
Subscription::batch([
|
||||
let mut subscriptions = vec![
|
||||
event::listen_with(|event, status, window_id| match event {
|
||||
event::Event::Keyboard(keyboard::Event::KeyPressed { modifiers, key, .. }) => {
|
||||
match status {
|
||||
|
|
@ -3046,6 +3065,15 @@ impl Application for App {
|
|||
Some(dialog) => dialog.subscription(),
|
||||
None => Subscription::none(),
|
||||
},
|
||||
])
|
||||
];
|
||||
|
||||
if let Some(auto_scroll) = self.auto_scroll {
|
||||
subscriptions.push(
|
||||
iced::time::every(time::Duration::from_millis(10))
|
||||
.map(move |_| Message::Scroll(auto_scroll)),
|
||||
);
|
||||
}
|
||||
|
||||
Subscription::batch(subscriptions)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
106
src/tab.rs
106
src/tab.rs
|
|
@ -9,14 +9,13 @@ use cosmic_text::{Attrs, Buffer, Cursor, Edit, Selection, Shaping, SyntaxEditor,
|
|||
use notify::Watcher;
|
||||
use regex::Regex;
|
||||
use std::{
|
||||
io::Write,
|
||||
fs,
|
||||
io::Write,
|
||||
path::PathBuf,
|
||||
process::{Command, Stdio},
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
|
||||
use crate::{fl, git::GitDiff, Config, SYNTAX_SYSTEM};
|
||||
|
||||
pub enum Tab {
|
||||
|
|
@ -147,65 +146,70 @@ impl EditorTab {
|
|||
}
|
||||
|
||||
pub fn save(&mut self) {
|
||||
if let Some(path) = &self.path_opt {
|
||||
let mut editor = self.editor.lock().unwrap();
|
||||
let mut text = String::new();
|
||||
|
||||
editor.with_buffer(|buffer| {
|
||||
for line in buffer.lines.iter() {
|
||||
text.push_str(line.text());
|
||||
text.push_str(line.ending().as_str());
|
||||
}
|
||||
});
|
||||
if let Some(path) = &self.path_opt {
|
||||
let mut editor = self.editor.lock().unwrap();
|
||||
let mut text = String::new();
|
||||
|
||||
match fs::write(path, &text) {
|
||||
Ok(()) => {
|
||||
editor.save_point();
|
||||
log::info!("saved {:?}", path);
|
||||
}
|
||||
Err(err) => {
|
||||
if err.kind() == std::io::ErrorKind::PermissionDenied {
|
||||
log::warn!("Permission denied. Attempting to save with pkexec.");
|
||||
editor.with_buffer(|buffer| {
|
||||
for line in buffer.lines.iter() {
|
||||
text.push_str(line.text());
|
||||
text.push_str(line.ending().as_str());
|
||||
}
|
||||
});
|
||||
|
||||
if let Ok(mut output) = Command::new("pkexec")
|
||||
.arg("tee")
|
||||
.arg(path)
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::null()) // Redirect stdout to /dev/null
|
||||
.stderr(Stdio::inherit()) // Retain stderr for error visibility
|
||||
.spawn()
|
||||
{
|
||||
if let Some(mut stdin) = output.stdin.take() {
|
||||
if let Err(e) = stdin.write_all(text.as_bytes()) {
|
||||
log::error!("Failed to write to stdin: {}", e);
|
||||
match fs::write(path, &text) {
|
||||
Ok(()) => {
|
||||
editor.save_point();
|
||||
log::info!("saved {:?}", path);
|
||||
}
|
||||
Err(err) => {
|
||||
if err.kind() == std::io::ErrorKind::PermissionDenied {
|
||||
log::warn!("Permission denied. Attempting to save with pkexec.");
|
||||
|
||||
if let Ok(mut output) = Command::new("pkexec")
|
||||
.arg("tee")
|
||||
.arg(path)
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::null()) // Redirect stdout to /dev/null
|
||||
.stderr(Stdio::inherit()) // Retain stderr for error visibility
|
||||
.spawn()
|
||||
{
|
||||
if let Some(mut stdin) = output.stdin.take() {
|
||||
if let Err(e) = stdin.write_all(text.as_bytes()) {
|
||||
log::error!("Failed to write to stdin: {}", e);
|
||||
}
|
||||
} else {
|
||||
log::error!("Failed to access stdin of pkexec process.");
|
||||
}
|
||||
} else {
|
||||
log::error!("Failed to access stdin of pkexec process.");
|
||||
}
|
||||
|
||||
// Ensure the child process is reaped
|
||||
match output.wait() {
|
||||
Ok(status) => {
|
||||
if status.success() {
|
||||
// Mark the editor's state as saved if the process succeeds
|
||||
editor.save_point();
|
||||
log::info!("File saved successfully with pkexec.");
|
||||
} else {
|
||||
log::error!("pkexec process exited with a non-zero status: {:?}", status);
|
||||
// Ensure the child process is reaped
|
||||
match output.wait() {
|
||||
Ok(status) => {
|
||||
if status.success() {
|
||||
// Mark the editor's state as saved if the process succeeds
|
||||
editor.save_point();
|
||||
log::info!("File saved successfully with pkexec.");
|
||||
} else {
|
||||
log::error!(
|
||||
"pkexec process exited with a non-zero status: {:?}",
|
||||
status
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to wait on pkexec process: {}", e);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to wait on pkexec process: {}", e);
|
||||
}
|
||||
} else {
|
||||
log::error!(
|
||||
"Failed to spawn pkexec process. Check permissions or path."
|
||||
);
|
||||
}
|
||||
} else {
|
||||
log::error!("Failed to spawn pkexec process. Check permissions or path.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::warn!("tab has no path yet");
|
||||
} else {
|
||||
log::warn!("tab has no path yet");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ pub struct TextBox<'a, Message> {
|
|||
metrics: Metrics,
|
||||
id: Option<Id>,
|
||||
padding: Padding,
|
||||
on_auto_scroll: Option<Box<dyn Fn(Option<f32>) -> Message + 'a>>,
|
||||
on_changed: Option<Message>,
|
||||
click_timing: Duration,
|
||||
has_context_menu: bool,
|
||||
|
|
@ -60,6 +61,7 @@ where
|
|||
metrics,
|
||||
id: None,
|
||||
padding: Padding::new(0.0),
|
||||
on_auto_scroll: None,
|
||||
on_changed: None,
|
||||
click_timing: Duration::from_millis(500),
|
||||
has_context_menu: false,
|
||||
|
|
@ -79,6 +81,11 @@ where
|
|||
self
|
||||
}
|
||||
|
||||
pub fn on_auto_scroll(mut self, on_auto_scroll: impl Fn(Option<f32>) -> Message + 'a) -> Self {
|
||||
self.on_auto_scroll = Some(Box::new(on_auto_scroll));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_changed(mut self, on_changed: Message) -> Self {
|
||||
self.on_changed = Some(on_changed);
|
||||
self
|
||||
|
|
@ -1109,6 +1116,9 @@ where
|
|||
Event::Mouse(MouseEvent::ButtonReleased(Button::Left)) => {
|
||||
state.dragging = None;
|
||||
status = Status::Captured;
|
||||
if let Some(on_auto_scroll) = &self.on_auto_scroll {
|
||||
shell.publish(on_auto_scroll(None));
|
||||
}
|
||||
}
|
||||
Event::Mouse(MouseEvent::CursorMoved { .. }) => {
|
||||
if let Some(dragging) = &state.dragging {
|
||||
|
|
@ -1124,6 +1134,20 @@ where
|
|||
x: x as i32,
|
||||
y: y as i32,
|
||||
});
|
||||
let auto_scroll = editor.with_buffer(|buffer| {
|
||||
//TODO: ideal auto scroll speed
|
||||
let speed = 10.0;
|
||||
if y < 0.0 {
|
||||
Some(y * speed)
|
||||
} else if y > buffer.size().1.unwrap_or(0.0) {
|
||||
Some((y - buffer.size().1.unwrap_or(0.0)) * speed)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
if let Some(on_auto_scroll) = &self.on_auto_scroll {
|
||||
shell.publish(on_auto_scroll(auto_scroll));
|
||||
}
|
||||
}
|
||||
Dragging::ScrollbarV {
|
||||
start_y,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue