Skip to content

Commit 04f2812

Browse files
committed
Implement drag scroll, fixes #154
1 parent f4ef25e commit 04f2812

File tree

3 files changed

+110
-54
lines changed

3 files changed

+110
-54
lines changed

src/main.rs

+30-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use cosmic::{
99
cosmic_theme, executor,
1010
font::Font,
1111
iced::{
12+
self,
1213
advanced::graphics::text::font_system,
1314
clipboard, event,
1415
futures::{self, SinkExt},
@@ -318,6 +319,7 @@ enum NewTab {
318319
#[derive(Clone, Debug)]
319320
pub enum Message {
320321
AppTheme(AppTheme),
322+
AutoScroll(Option<f32>),
321323
Config(Config),
322324
ConfigState(ConfigState),
323325
CloseFile,
@@ -374,6 +376,7 @@ pub enum Message {
374376
SaveAll,
375377
SaveAsDialog(Option<segmented_button::Entity>),
376378
SaveAsResult(segmented_button::Entity, DialogResult),
379+
Scroll(f32),
377380
SelectAll,
378381
SystemThemeModeChange(cosmic_theme::ThemeMode),
379382
SyntaxTheme(usize, bool),
@@ -438,6 +441,7 @@ pub struct App {
438441
theme_names: Vec<String>,
439442
context_page: ContextPage,
440443
text_box_id: widget::Id,
444+
auto_scroll: Option<f32>,
441445
dialog_opt: Option<Dialog<Message>>,
442446
dialog_page_opt: Option<DialogPage>,
443447
find_opt: Option<bool>,
@@ -1323,6 +1327,7 @@ impl Application for App {
13231327
theme_names,
13241328
context_page: ContextPage::Settings,
13251329
text_box_id: widget::Id::unique(),
1330+
auto_scroll: None,
13261331
dialog_opt: None,
13271332
dialog_page_opt: None,
13281333
find_opt: None,
@@ -1566,6 +1571,9 @@ impl Application for App {
15661571
self.config.app_theme = app_theme;
15671572
return self.save_config();
15681573
}
1574+
Message::AutoScroll(auto_scroll) => {
1575+
self.auto_scroll = auto_scroll;
1576+
}
15691577
Message::Config(config) => {
15701578
if config != self.config {
15711579
log::info!("update config");
@@ -2330,6 +2338,16 @@ impl Application for App {
23302338
editor.set_selection(selection);
23312339
}
23322340
}
2341+
Message::Scroll(auto_scroll) => {
2342+
if let Some(Tab::Editor(tab)) = self.active_tab_mut() {
2343+
let mut editor = tab.editor.lock().unwrap();
2344+
editor.with_buffer_mut(|buffer| {
2345+
let mut scroll = buffer.scroll();
2346+
scroll.vertical += auto_scroll;
2347+
buffer.set_scroll(scroll);
2348+
});
2349+
}
2350+
}
23332351
Message::SystemThemeModeChange(_theme_mode) => {
23342352
return self.update_config();
23352353
}
@@ -2687,6 +2705,7 @@ impl Application for App {
26872705
Some(Tab::Editor(tab)) => {
26882706
let mut text_box = text_box(&tab.editor, self.config.metrics())
26892707
.id(self.text_box_id.clone())
2708+
.on_auto_scroll(Message::AutoScroll)
26902709
.on_changed(Message::TabChanged(tab_id))
26912710
.has_context_menu(tab.context_menu.is_some())
26922711
.on_context_menu(move |position_opt| {
@@ -2928,7 +2947,7 @@ impl Application for App {
29282947
struct ConfigStateSubscription;
29292948
struct ThemeSubscription;
29302949

2931-
Subscription::batch([
2950+
let mut subscriptions = vec![
29322951
event::listen_with(|event, status, window_id| match event {
29332952
event::Event::Keyboard(keyboard::Event::KeyPressed { modifiers, key, .. }) => {
29342953
match status {
@@ -3046,6 +3065,15 @@ impl Application for App {
30463065
Some(dialog) => dialog.subscription(),
30473066
None => Subscription::none(),
30483067
},
3049-
])
3068+
];
3069+
3070+
if let Some(auto_scroll) = self.auto_scroll {
3071+
subscriptions.push(
3072+
iced::time::every(time::Duration::from_millis(10))
3073+
.map(move |_| Message::Scroll(auto_scroll)),
3074+
);
3075+
}
3076+
3077+
Subscription::batch(subscriptions)
30503078
}
30513079
}

src/tab.rs

+56-52
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,13 @@ use cosmic_text::{Attrs, Buffer, Cursor, Edit, Selection, Shaping, SyntaxEditor,
99
use notify::Watcher;
1010
use regex::Regex;
1111
use std::{
12-
io::Write,
1312
fs,
13+
io::Write,
1414
path::PathBuf,
1515
process::{Command, Stdio},
1616
sync::{Arc, Mutex},
1717
};
1818

19-
2019
use crate::{fl, git::GitDiff, Config, SYNTAX_SYSTEM};
2120

2221
pub enum Tab {
@@ -147,65 +146,70 @@ impl EditorTab {
147146
}
148147

149148
pub fn save(&mut self) {
150-
if let Some(path) = &self.path_opt {
151-
let mut editor = self.editor.lock().unwrap();
152-
let mut text = String::new();
153-
154-
editor.with_buffer(|buffer| {
155-
for line in buffer.lines.iter() {
156-
text.push_str(line.text());
157-
text.push_str(line.ending().as_str());
158-
}
159-
});
149+
if let Some(path) = &self.path_opt {
150+
let mut editor = self.editor.lock().unwrap();
151+
let mut text = String::new();
160152

161-
match fs::write(path, &text) {
162-
Ok(()) => {
163-
editor.save_point();
164-
log::info!("saved {:?}", path);
165-
}
166-
Err(err) => {
167-
if err.kind() == std::io::ErrorKind::PermissionDenied {
168-
log::warn!("Permission denied. Attempting to save with pkexec.");
169-
170-
if let Ok(mut output) = Command::new("pkexec")
171-
.arg("tee")
172-
.arg(path)
173-
.stdin(Stdio::piped())
174-
.stdout(Stdio::null()) // Redirect stdout to /dev/null
175-
.stderr(Stdio::inherit()) // Retain stderr for error visibility
176-
.spawn()
177-
{
178-
if let Some(mut stdin) = output.stdin.take() {
179-
if let Err(e) = stdin.write_all(text.as_bytes()) {
180-
log::error!("Failed to write to stdin: {}", e);
181-
}
182-
} else {
183-
log::error!("Failed to access stdin of pkexec process.");
184-
}
153+
editor.with_buffer(|buffer| {
154+
for line in buffer.lines.iter() {
155+
text.push_str(line.text());
156+
text.push_str(line.ending().as_str());
157+
}
158+
});
185159

186-
// Ensure the child process is reaped
187-
match output.wait() {
188-
Ok(status) => {
189-
if status.success() {
190-
// Mark the editor's state as saved if the process succeeds
191-
editor.save_point();
192-
log::info!("File saved successfully with pkexec.");
193-
} else {
194-
log::error!("pkexec process exited with a non-zero status: {:?}", status);
160+
match fs::write(path, &text) {
161+
Ok(()) => {
162+
editor.save_point();
163+
log::info!("saved {:?}", path);
164+
}
165+
Err(err) => {
166+
if err.kind() == std::io::ErrorKind::PermissionDenied {
167+
log::warn!("Permission denied. Attempting to save with pkexec.");
168+
169+
if let Ok(mut output) = Command::new("pkexec")
170+
.arg("tee")
171+
.arg(path)
172+
.stdin(Stdio::piped())
173+
.stdout(Stdio::null()) // Redirect stdout to /dev/null
174+
.stderr(Stdio::inherit()) // Retain stderr for error visibility
175+
.spawn()
176+
{
177+
if let Some(mut stdin) = output.stdin.take() {
178+
if let Err(e) = stdin.write_all(text.as_bytes()) {
179+
log::error!("Failed to write to stdin: {}", e);
195180
}
181+
} else {
182+
log::error!("Failed to access stdin of pkexec process.");
196183
}
197-
Err(e) => {
198-
log::error!("Failed to wait on pkexec process: {}", e);
184+
185+
// Ensure the child process is reaped
186+
match output.wait() {
187+
Ok(status) => {
188+
if status.success() {
189+
// Mark the editor's state as saved if the process succeeds
190+
editor.save_point();
191+
log::info!("File saved successfully with pkexec.");
192+
} else {
193+
log::error!(
194+
"pkexec process exited with a non-zero status: {:?}",
195+
status
196+
);
197+
}
198+
}
199+
Err(e) => {
200+
log::error!("Failed to wait on pkexec process: {}", e);
201+
}
199202
}
203+
} else {
204+
log::error!(
205+
"Failed to spawn pkexec process. Check permissions or path."
206+
);
200207
}
201-
} else {
202-
log::error!("Failed to spawn pkexec process. Check permissions or path.");
203208
}
204209
}
205210
}
206-
}
207-
} else {
208-
log::warn!("tab has no path yet");
211+
} else {
212+
log::warn!("tab has no path yet");
209213
}
210214
}
211215

src/text_box.rs

+24
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ pub struct TextBox<'a, Message> {
4242
metrics: Metrics,
4343
id: Option<Id>,
4444
padding: Padding,
45+
on_auto_scroll: Option<Box<dyn Fn(Option<f32>) -> Message + 'a>>,
4546
on_changed: Option<Message>,
4647
click_timing: Duration,
4748
has_context_menu: bool,
@@ -60,6 +61,7 @@ where
6061
metrics,
6162
id: None,
6263
padding: Padding::new(0.0),
64+
on_auto_scroll: None,
6365
on_changed: None,
6466
click_timing: Duration::from_millis(500),
6567
has_context_menu: false,
@@ -79,6 +81,11 @@ where
7981
self
8082
}
8183

84+
pub fn on_auto_scroll(mut self, on_auto_scroll: impl Fn(Option<f32>) -> Message + 'a) -> Self {
85+
self.on_auto_scroll = Some(Box::new(on_auto_scroll));
86+
self
87+
}
88+
8289
pub fn on_changed(mut self, on_changed: Message) -> Self {
8390
self.on_changed = Some(on_changed);
8491
self
@@ -1109,6 +1116,9 @@ where
11091116
Event::Mouse(MouseEvent::ButtonReleased(Button::Left)) => {
11101117
state.dragging = None;
11111118
status = Status::Captured;
1119+
if let Some(on_auto_scroll) = &self.on_auto_scroll {
1120+
shell.publish(on_auto_scroll(None));
1121+
}
11121122
}
11131123
Event::Mouse(MouseEvent::CursorMoved { .. }) => {
11141124
if let Some(dragging) = &state.dragging {
@@ -1124,6 +1134,20 @@ where
11241134
x: x as i32,
11251135
y: y as i32,
11261136
});
1137+
let auto_scroll = editor.with_buffer(|buffer| {
1138+
//TODO: ideal auto scroll speed
1139+
let speed = 10.0;
1140+
if y < 0.0 {
1141+
Some(y * speed)
1142+
} else if y > buffer.size().1.unwrap_or(0.0) {
1143+
Some((y - buffer.size().1.unwrap_or(0.0)) * speed)
1144+
} else {
1145+
None
1146+
}
1147+
});
1148+
if let Some(on_auto_scroll) = &self.on_auto_scroll {
1149+
shell.publish(on_auto_scroll(auto_scroll));
1150+
}
11271151
}
11281152
Dragging::ScrollbarV {
11291153
start_y,

0 commit comments

Comments
 (0)