Files
feresaria/shell/ferese-settings/src/navigation.rs
T

138 lines
4.0 KiB
Rust

use cosmic::iced::advanced::widget::Id;
use cosmic::iced::advanced::widget::operation::{Operation, Scrollable};
use cosmic::iced::{Rectangle, Vector};
pub(super) fn gallery_list(
appearance: Option<ferese_config::theme::Appearance>,
custom: bool,
) -> &'static str {
match (appearance, custom) {
(Some(ferese_config::theme::Appearance::Light), false) => "theme-light",
(Some(ferese_config::theme::Appearance::Dark), false) => "theme-dark",
(None, false) => "theme",
(Some(ferese_config::theme::Appearance::Light), true) => "theme-custom-light",
(Some(ferese_config::theme::Appearance::Dark), true) => "theme-custom-dark",
(None, true) => "theme-custom",
}
}
pub(super) fn gallery_row_id(list: &str, row: usize) -> Id {
Id::new(format!("{list}-row-{row}"))
}
pub(super) struct RevealRow {
target: Id,
bounds: Option<Rectangle>,
}
impl RevealRow {
pub(super) fn new(list: &str, row: usize) -> Self {
Self::for_target(gallery_row_id(list, row))
}
pub(super) fn for_target(target: Id) -> Self {
Self {
target,
bounds: None,
}
}
}
impl<M> Operation<M> for RevealRow {
fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation<M>)) {
operate(self);
}
fn container(&mut self, id: Option<&Id>, bounds: Rectangle) {
if id == Some(&self.target) {
self.bounds = Some(bounds);
}
}
fn scrollable(
&mut self,
id: Option<&Id>,
viewport: Rectangle,
_: Rectangle,
translation: Vector,
state: &mut dyn Scrollable,
) {
if id != Some(&Id::new("settings-content")) {
return;
}
if let Some(target) = self.bounds {
let visible = target.y - translation.y;
let delta = if visible < viewport.y {
visible - viewport.y
} else if visible + target.height > viewport.y + viewport.height {
visible + target.height - viewport.y - viewport.height
} else {
return;
};
state.scroll_to(cosmic::iced::widget::scrollable::AbsoluteOffset {
x: None,
y: Some((translation.y + delta).max(0.)),
});
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use cosmic::iced::advanced::widget::operation::scrollable::{AbsoluteOffset, RelativeOffset};
#[derive(Default)]
struct Scroll(Option<AbsoluteOffset<Option<f32>>>);
impl Scrollable for Scroll {
fn snap_to(&mut self, _: RelativeOffset<Option<f32>>) {
panic!("not a snap");
}
fn scroll_to(&mut self, offset: AbsoluteOffset<Option<f32>>) {
self.0 = Some(offset);
}
fn scroll_by(&mut self, _: AbsoluteOffset, _: Rectangle, _: Rectangle) {
panic!("not a relative scroll");
}
}
#[test]
fn wrapping_and_home_end_reveal_only_as_much_as_needed() {
let viewport = Rectangle {
x: 0.,
y: 40.,
width: 600.,
height: 500.,
};
for (top, height, offset, expected) in [
(1000., 200., 0., Some(660.)),
(40., 200., 500., Some(0.)),
(100., 200., 0., None),
] {
let mut operation = RevealRow::new("theme", 10);
let target = operation.target.clone();
Operation::<()>::container(
&mut operation,
Some(&target),
Rectangle {
y: top,
height,
..viewport
},
);
let mut state = Scroll::default();
Operation::<()>::scrollable(
&mut operation,
Some(&Id::new("settings-content")),
viewport,
viewport,
Vector::new(0., offset),
&mut state,
);
assert_eq!(state.0.and_then(|offset| offset.y), expected);
}
}
}