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