openpencil/crates/op-host-web/src/widget_host/scroll.rs

309 lines
11 KiB
Rust

//! Web wheel-scroll routing for the side panels — extracted from
//! `widget_host.rs` so the spine stays under the 800-line cap.
//! Mirrors the native host's `widget_host/scroll.rs`.
use op_editor_ui::util::scroll_by_max;
use op_editor_ui::widgets::{LayerPanel, PropertyPanel, TOP_BAR_HEIGHT};
use op_editor_ui::{Point2D, Rect};
use super::WidgetHost;
impl WidgetHost {
/// Scroll the floating VariablesPanel row list when the wheel
/// fires over the open panel (TS `overflow-y-auto` rows region).
/// The whole panel rect swallows the event so a wheel over its
/// header can't zoom the canvas beneath. Mirrors the native host.
pub(in crate::widget_host) fn try_scroll_variables_panel(
&mut self,
x: f32,
y: f32,
delta_y: f32,
viewport_width: f32,
viewport_height: f32,
) -> bool {
if !self.editor_state.editor_ui.variables_panel_open {
return false;
}
let Some(panel_rect) = self.variables_panel_rect(viewport_width, viewport_height) else {
return false;
};
if !(panel_rect).contains(Point2D::new(x, y)) {
return false;
}
use op_editor_ui::widgets::variables_panel::VariablesPanel;
let panel = VariablesPanel::for_editor(&self.editor_state);
let max = panel.max_scroll(panel_rect);
if scroll_by_max(
&mut self.editor_state.editor_ui.variables_scroll,
-delta_y,
max,
) {
self.mark_dirty();
}
true
}
pub(in crate::widget_host) fn try_scroll_locale_picker(
&mut self,
x: f32,
y: f32,
delta_y: f32,
viewport_width: f32,
) -> bool {
if !self.editor_state.editor_ui.locale_picker.open {
return false;
}
if !(self.locale_picker_rect(viewport_width)).contains(Point2D::new(x, y)) {
return false;
}
let ui = &mut self.editor_state.editor_ui.locale_picker;
let next = (ui.scroll.offset - delta_y)
.clamp(0.0, op_editor_ui::widgets::LocalePicker::max_scroll());
let changed = next != ui.scroll.offset || ui.hover.is_some();
ui.scroll.offset = next;
ui.hover = None;
if changed {
self.mark_dirty();
}
true
}
pub(in crate::widget_host) fn try_scroll_design_md_panel(
&mut self,
x: f32,
y: f32,
delta_y: f32,
viewport_width: f32,
viewport_height: f32,
) -> bool {
let Some(panel_rect) = self.design_md_panel_rect(viewport_width, viewport_height) else {
return false;
};
if !(panel_rect).contains(Point2D::new(x, y)) {
return false;
}
let Some(panel) = op_editor_ui::widgets::DesignMdPanel::for_editor(&self.editor_state)
else {
return false;
};
let max = panel.max_scroll(panel_rect);
if scroll_by_max(
&mut self.editor_state.editor_ui.design_md_scroll,
-delta_y,
max,
) {
self.mark_dirty();
}
true
}
/// Scroll the open icon picker's list when the pointer is over its panel.
/// The picker loads up to 120 local + remote icons — far more than fit — so
/// the list must scroll. Mirrors the native host.
pub(in crate::widget_host) fn try_scroll_icon_picker(
&mut self,
x: f32,
y: f32,
delta_y: f32,
viewport_width: f32,
viewport_height: f32,
) -> bool {
if !self.editor_state.editor_ui.icon_picker.open {
return false;
}
use op_editor_ui::widgets::icon_picker_panel::IconPickerPanel;
let Some(rect) = self.icon_picker_panel_rect(viewport_width, viewport_height) else {
return false;
};
if !(rect).contains(Point2D::new(x, y)) {
return false;
}
if let Some(panel) = IconPickerPanel::for_editor(&self.editor_state) {
let max = panel.icon_picker_max_scroll(rect);
let scroll = &mut self.editor_state.editor_ui.icon_picker.scroll;
let next = (scroll.offset - delta_y).clamp(0.0, max);
if next != scroll.offset {
scroll.offset = next;
self.mark_dirty();
}
}
true
}
/// Scroll the right-rail PropertyPanel when a wheel lands over
/// it. Returns `true` when the cursor was over the inspector.
pub(in crate::widget_host) fn try_scroll_property_panel(
&mut self,
x: f32,
y: f32,
delta_y: f32,
viewport_width: f32,
viewport_height: f32,
) -> bool {
let Some(panel) = PropertyPanel::for_selection(&self.editor_state) else {
return false;
};
let pw = self.editor_state.editor_ui.property_panel_width;
let property_rect = Rect {
origin: Point2D::new(viewport_width - pw, TOP_BAR_HEIGHT),
size: Point2D::new(pw, (viewport_height - TOP_BAR_HEIGHT).max(0.0)),
};
// A wheel over the open font-family picker scrolls ITS list,
// not the panel behind it (mirrors the native host).
if self.editor_state.editor_ui.font_picker.open
&& panel.font_picker_contains(property_rect, Point2D::new(x, y))
{
let max = panel.font_picker_max_scroll(property_rect);
let ui = &mut self.editor_state.editor_ui;
let next = (ui.font_picker.scroll.offset + delta_y).clamp(0.0, max);
if next != ui.font_picker.scroll.offset {
ui.font_picker.scroll.offset = next;
ui.font_picker.hover = None;
self.mark_dirty();
}
return true;
}
if !(property_rect).contains(Point2D::new(x, y)) {
return false;
}
if matches!(
self.editor_state.editor_ui.property_tab,
op_editor_core::PropertyTab::Code
) {
let point = Point2D::new(x, y);
let (band_top, band_bottom) =
op_editor_ui::widgets::property_panel_code::framework_row_band(
property_rect.origin.y,
);
if y >= band_top && y <= band_bottom {
let max = op_editor_ui::widgets::property_panel_code::framework_row_overflow(pw);
let cg = &mut self.editor_state.codegen;
if scroll_by_max(&mut cg.framework_scroll, -delta_y, max) {
self.mark_dirty();
}
return true;
}
if op_editor_ui::widgets::property_panel_code::code_preview_rect(
property_rect,
&self.editor_state.codegen,
)
.is_some_and(|rect| (rect).contains(point))
{
let max = op_editor_ui::widgets::property_panel_code::code_preview_max_scroll(
property_rect,
&self.editor_state.codegen,
)
.unwrap_or(0.0);
let cg = &mut self.editor_state.codegen;
if scroll_by_max(&mut cg.code_scroll, -delta_y, max) {
self.mark_dirty();
}
return true;
}
}
let max = (panel.content_height(property_rect) - property_rect.size.y).max(0.0);
if scroll_by_max(
&mut self.editor_state.editor_ui.property_panel_scroll,
-delta_y,
max,
) {
self.mark_dirty();
}
true
}
/// Scroll the left-rail LayerPanel when a wheel lands over it —
/// the Pages section above the Layers row viewport, otherwise
/// the Layers section. Returns `true` when over the panel.
pub(in crate::widget_host) fn try_scroll_layer_panel(
&mut self,
x: f32,
y: f32,
delta_x: f32,
delta_y: f32,
viewport_height: f32,
) -> bool {
if !self.editor_state.editor_ui.sidebar_open {
return false;
}
let pw = self.editor_state.editor_ui.layer_panel_width;
let rect = Rect {
origin: Point2D::new(0.0, TOP_BAR_HEIGHT),
size: Point2D::new(pw, (viewport_height - TOP_BAR_HEIGHT).max(0.0)),
};
if !(rect).contains(Point2D::new(x, y)) {
return false;
}
let r = LayerPanel::from_editor(&self.editor_state).regions(rect);
let mut changed = false;
if y >= r.layers_rows_top {
if delta_y != 0.0
&& scroll_by_max(
&mut self.editor_state.editor_ui.layer_layers_scroll,
-delta_y,
r.layers.max_offset,
)
{
changed = true;
}
if delta_x != 0.0
&& scroll_by_max(
&mut self.editor_state.editor_ui.layer_layers_h_scroll,
-delta_x,
r.layers.max_horizontal_offset,
)
{
changed = true;
}
} else {
if delta_y != 0.0
&& scroll_by_max(
&mut self.editor_state.editor_ui.layer_pages_scroll,
-delta_y,
r.pages.max_offset,
)
{
changed = true;
}
if delta_x != 0.0
&& scroll_by_max(
&mut self.editor_state.editor_ui.layer_pages_h_scroll,
-delta_x,
r.pages.max_horizontal_offset,
)
{
changed = true;
}
}
if changed {
self.mark_dirty();
}
true
}
pub(in crate::widget_host) fn scroll_layer_panel_selection_into_view(
&mut self,
viewport_height: f32,
) -> bool {
if !self.editor_state.editor_ui.sidebar_open {
return false;
}
let selected = self.editor_state.selection.anchor.clone();
if !selected.is_real() {
return false;
}
let rect = self.layer_panel_rect(viewport_height);
let panel = LayerPanel::from_editor(&self.editor_state);
let Some(next) = panel.layers_offset_revealing(rect, &selected) else {
return false;
};
let scroll = &mut self.editor_state.editor_ui.layer_layers_scroll;
if (scroll.offset - next).abs() <= f32::EPSILON {
return false;
}
scroll.offset = next;
self.mark_dirty();
true
}
}