Merge pull request #462 from pop-os/scroll-fixes

Fix horizontal scrolling with wheel or touchpad and improve scrollbar behavior
This commit is contained in:
Jeremy Soller 2025-11-12 10:14:00 -07:00 committed by GitHub
commit e66aa263a0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -324,7 +324,7 @@ where
) -> mouse::Interaction { ) -> mouse::Interaction {
let state = tree.state.downcast_ref::<State>(); let state = tree.state.downcast_ref::<State>();
if let Some(Dragging::ScrollbarV { .. }) = &state.dragging { if let Some(Dragging::ScrollbarV { .. } | Dragging::ScrollbarH { .. }) = &state.dragging {
return mouse::Interaction::Idle; return mouse::Interaction::Idle;
} }
@ -367,12 +367,12 @@ where
let mut editor = self.editor.lock().unwrap(); let mut editor = self.editor.lock().unwrap();
let cosmic_theme = theme.cosmic(); let cosmic_theme = theme.cosmic();
let scrollbar_w = cosmic_theme.spacing.space_xxs as i32; let scrollbar_size = cosmic_theme.spacing.space_xxs as i32;
let view_position = layout.position() + [self.padding.left, self.padding.top].into(); let view_position = layout.position() + [self.padding.left, self.padding.top].into();
let view_w = cmp::min(viewport.width as i32, layout.bounds().width as i32) let view_w = cmp::min(viewport.width as i32, layout.bounds().width as i32)
- self.padding.horizontal() as i32 - self.padding.horizontal() as i32
- scrollbar_w; - scrollbar_size;
let view_h = cmp::min(viewport.height as i32, layout.bounds().height as i32) let view_h = cmp::min(viewport.height as i32, layout.bounds().height as i32)
- self.padding.vertical() as i32; - self.padding.vertical() as i32;
@ -597,7 +597,7 @@ where
let rect = Rectangle::new( let rect = Rectangle::new(
[image_w as f32 / scale_factor, start_y as f32 / scale_factor].into(), [image_w as f32 / scale_factor, start_y as f32 / scale_factor].into(),
Size::new( Size::new(
scrollbar_w as f32, scrollbar_size as f32,
(end_y as f32 - start_y as f32) / scale_factor, (end_y as f32 - start_y as f32) / scale_factor,
), ),
); );
@ -606,17 +606,17 @@ where
let (buffer_w_opt, buffer_h_opt) = buffer.size(); let (buffer_w_opt, buffer_h_opt) = buffer.size();
let buffer_w = buffer_w_opt.unwrap_or(0.0); let buffer_w = buffer_w_opt.unwrap_or(0.0);
let buffer_h = buffer_h_opt.unwrap_or(0.0); let buffer_h = buffer_h_opt.unwrap_or(0.0);
let scrollbar_h_width = image_w as f32 / scale_factor - scrollbar_w as f32; let scrollbar_h_width = (image_w as f32) / scale_factor;
if buffer_w < max_line_width { if buffer_w < max_line_width {
let rect = Rectangle::new( let rect = Rectangle::new(
[ [
(buffer.scroll().horizontal / max_line_width) * scrollbar_h_width, (buffer.scroll().horizontal / max_line_width) * scrollbar_h_width,
buffer_h / scale_factor - scrollbar_w as f32, buffer_h / scale_factor - scrollbar_size as f32,
] ]
.into(), .into(),
Size::new( Size::new(
(buffer_w / max_line_width) * scrollbar_h_width, (buffer_w / max_line_width) * scrollbar_h_width,
scrollbar_w as f32, scrollbar_size as f32,
), ),
); );
state.scrollbar_h_rect.set(Some(rect)); state.scrollbar_h_rect.set(Some(rect));
@ -643,12 +643,6 @@ where
// Draw editor UI // Draw editor UI
renderer.with_translation(Vector::new(view_position.x, view_position.y), |renderer| { renderer.with_translation(Vector::new(view_position.x, view_position.y), |renderer| {
renderer.with_transformation(Transformation::scale(1.0 / scale_factor), |renderer| { renderer.with_transformation(Transformation::scale(1.0 / scale_factor), |renderer| {
renderer.with_layer(
Rectangle::new(
Point::new(0.0, 0.0),
Size::new(image_w as f32, image_h as f32),
),
|renderer| {
// Draw cached image (only has line numbers) // Draw cached image (only has line numbers)
if let Some(ref handle) = *handle_opt { if let Some(ref handle) = *handle_opt {
let image_size = image::Renderer::measure_image(renderer, handle); let image_size = image::Renderer::measure_image(renderer, handle);
@ -668,9 +662,10 @@ where
// Calculate editor position // Calculate editor position
let scroll_x = editor.with_buffer(|buffer| buffer.scroll().horizontal); let scroll_x = editor.with_buffer(|buffer| buffer.scroll().horizontal);
let pos = Point::new(editor_offset_x as f32, 0.0); let pos = Point::new(editor_offset_x as f32 - scroll_x, 0.0);
let size = Size::new((image_w - editor_offset_x) as f32, image_h as f32); let size = Size::new((image_w - editor_offset_x) as f32, image_h as f32);
let clip_bounds = Rectangle::new(Point::new(editor_offset_x as f32, 0.0), size);
renderer.with_layer(clip_bounds, |renderer| {
// Create custom renderer for rectangles // Create custom renderer for rectangles
let mut custom_renderer = CustomRenderer { renderer, pos }; let mut custom_renderer = CustomRenderer { renderer, pos };
@ -714,17 +709,16 @@ where
cosmic_text::BufferRef::Arc(buffer) => { cosmic_text::BufferRef::Arc(buffer) => {
renderer.fill_raw(Raw { renderer.fill_raw(Raw {
buffer: Arc::downgrade(&buffer), buffer: Arc::downgrade(&buffer),
position: pos - Vector::new(scroll_x, 0.0), position: pos,
color: Color::new(1.0, 1.0, 1.0, 1.0), color: Color::new(1.0, 1.0, 1.0, 1.0),
clip_bounds: Rectangle::new(pos, size), clip_bounds,
}); });
} }
_ => { _ => {
log::error!("cosmic-text buffer not an Arc"); log::error!("cosmic-text buffer not an Arc");
} }
} }
}, })
)
}) })
}); });
@ -820,7 +814,7 @@ where
// Draw horizontal scrollbar // Draw horizontal scrollbar
//TODO: reduce repitition //TODO: reduce repitition
if let Some(scrollbar_h_rect) = state.scrollbar_h_rect.get() { if let Some(scrollbar_h_rect) = state.scrollbar_h_rect.get() {
/*TODO: horizontal scrollbar track? /*
// neutral_3, 0.7 // neutral_3, 0.7
let track_color = cosmic_theme let track_color = cosmic_theme
.palette .palette
@ -832,9 +826,12 @@ where
renderer.fill_quad( renderer.fill_quad(
Quad { Quad {
bounds: Rectangle::new( bounds: Rectangle::new(
Point::new(image_position.x, image_position.y + scrollbar_h_rect.y), Point::new(
image_position.x + scrollbar_h_rect.x,
image_position.y + scrollbar_h_rect.y,
),
Size::new( Size::new(
layout.bounds().width - scrollbar_w as f32, layout.bounds().width - scrollbar_h_rect.x - scrollbar_size as f32,
scrollbar_h_rect.height, scrollbar_h_rect.height,
), ),
), ),
@ -1095,7 +1092,10 @@ where
// Do this first as the horizontal scrollbar is on top of the buffer // Do this first as the horizontal scrollbar is on top of the buffer
if let Some(scrollbar_h_rect) = state.scrollbar_h_rect.get() { if let Some(scrollbar_h_rect) = state.scrollbar_h_rect.get() {
if scrollbar_h_rect.contains(Point::new(x_logical, y_logical)) { if scrollbar_h_rect.contains(Point::new(x_logical, y_logical)) {
state.dragging = Some(Dragging::ScrollbarH { start_x: x }); state.dragging = Some(Dragging::ScrollbarH {
start_x: x,
start_scroll: editor.with_buffer(|buffer| buffer.scroll()),
});
} }
} }
@ -1232,7 +1232,10 @@ where
buffer.set_scroll(scroll); buffer.set_scroll(scroll);
}); });
} }
Dragging::ScrollbarH { start_x } => { Dragging::ScrollbarH {
start_x,
start_scroll,
} => {
editor.with_buffer_mut(|buffer| { editor.with_buffer_mut(|buffer| {
//TODO: store this in state? //TODO: store this in state?
let mut max_line_width = 0.0; let mut max_line_width = 0.0;
@ -1244,10 +1247,10 @@ where
let buffer_w = buffer.size().0.unwrap_or(0.0); let buffer_w = buffer.size().0.unwrap_or(0.0);
let mut scroll = buffer.scroll(); let mut scroll = buffer.scroll();
scroll.horizontal = (((x - start_x) / buffer_w) let scroll_offset = ((x - start_x) / buffer_w) * max_line_width;
* max_line_width) scroll.horizontal = (start_scroll.horizontal + scroll_offset)
.max(0.0) .min(max_line_width - buffer_w)
.min(max_line_width - buffer_w); .max(0.0);
buffer.set_scroll(scroll); buffer.set_scroll(scroll);
}); });
} }
@ -1258,15 +1261,33 @@ where
} }
Event::Mouse(MouseEvent::WheelScrolled { delta }) => { Event::Mouse(MouseEvent::WheelScrolled { delta }) => {
if let Some(_p) = cursor_position.position_in(layout.bounds()) { if let Some(_p) = cursor_position.position_in(layout.bounds()) {
let pixels = match delta { let (mut x, mut y) = match delta {
ScrollDelta::Lines { x: _, y } => { ScrollDelta::Lines { x, y } => {
//TODO: this adjustment is just a guess! //TODO: this adjustment is just a guess!
let metrics = editor.with_buffer(|buffer| buffer.metrics()); let metrics = editor.with_buffer(|buffer| buffer.metrics());
-y * metrics.line_height (-x * metrics.line_height, -y * metrics.line_height)
} }
ScrollDelta::Pixels { x: _, y } => -y, ScrollDelta::Pixels { x, y } => (-x, -y),
} * 4.0; };
editor.action(Action::Scroll { pixels }); x *= 4.0;
y *= 4.0;
editor.action(Action::Scroll { pixels: y });
editor.with_buffer_mut(|buffer| {
//TODO: store this in state?
let mut max_line_width = 0.0;
for run in buffer.layout_runs() {
if run.line_w > max_line_width {
max_line_width = run.line_w;
}
}
let buffer_w = buffer.size().0.unwrap_or(0.0);
let mut scroll = buffer.scroll();
scroll.horizontal = (scroll.horizontal + x)
.min(max_line_width - buffer_w)
.max(0.0);
buffer.set_scroll(scroll);
});
status = Status::Captured; status = Status::Captured;
} }
} }
@ -1302,10 +1323,11 @@ enum ClickKind {
Triple, Triple,
} }
#[derive(Debug)]
enum Dragging { enum Dragging {
Buffer, Buffer,
ScrollbarV { start_y: f32, start_scroll: Scroll }, ScrollbarV { start_y: f32, start_scroll: Scroll },
ScrollbarH { start_x: f32 }, ScrollbarH { start_x: f32, start_scroll: Scroll },
} }
pub struct State { pub struct State {