mirror of
https://github.com/flectar/mail.git
synced 2026-11-11 17:57:54 +00:00
150 lines
5.5 KiB
Rust
150 lines
5.5 KiB
Rust
//! Taps, right-clicks and long presses on sidebar rows, which also have menus.
|
|
use i_slint_core::input::TouchPhase;
|
|
use i_slint_core::lengths::LogicalPoint;
|
|
use i_slint_core::platform::InternalEvent;
|
|
use slint::ComponentHandle;
|
|
use slint::platform::{
|
|
Key, Platform, PointerEventButton, WindowAdapter, WindowEvent,
|
|
software_renderer::{MinimalSoftwareWindow, RepaintBufferType},
|
|
};
|
|
use std::{cell::Cell, rc::Rc, time::Duration};
|
|
|
|
slint::slint! {
|
|
import { SidebarItem, SidebarSectionHeader } from "../ui/components/sidebar-controls.slint";
|
|
|
|
export component SidebarRowHarness inherits Window {
|
|
width: 260px;
|
|
height: 300px;
|
|
in-out property <string> selected-scope;
|
|
in-out property <int> folder-toggles;
|
|
in-out property <int> new-subfolders;
|
|
in-out property <int> account-toggles;
|
|
in-out property <int> checks;
|
|
SidebarItem {
|
|
y: 0px;
|
|
width: parent.width;
|
|
label: "Projects";
|
|
scope: "Folder:5";
|
|
account-id: 1;
|
|
folder-id: 5;
|
|
has-children: true;
|
|
folder-menu-enabled: true;
|
|
can-create-child: true;
|
|
clicked(scope) => { root.selected-scope = scope; }
|
|
toggled(id, open) => { root.folder-toggles += 1; }
|
|
new-subfolder(account, folder, name) => { root.new-subfolders += 1; }
|
|
}
|
|
SidebarSectionHeader {
|
|
y: 40px;
|
|
width: parent.width;
|
|
label: "Flectar";
|
|
detail: "alex@flectar.com";
|
|
account-menu: true;
|
|
toggled(open) => { root.account-toggles += 1; }
|
|
check-mail => { root.checks += 1; }
|
|
}
|
|
}
|
|
}
|
|
|
|
struct Headless {
|
|
window: Rc<MinimalSoftwareWindow>,
|
|
clock: Rc<Cell<Duration>>,
|
|
}
|
|
|
|
impl Platform for Headless {
|
|
fn create_window_adapter(&self) -> Result<Rc<dyn WindowAdapter>, slint::PlatformError> {
|
|
Ok(self.window.clone())
|
|
}
|
|
|
|
fn duration_since_start(&self) -> Duration {
|
|
self.clock.get()
|
|
}
|
|
}
|
|
|
|
fn click(window: &slint::Window, x: f32, y: f32, button: PointerEventButton) {
|
|
let position = slint::LogicalPosition::new(x, y);
|
|
window.dispatch_event(WindowEvent::PointerMoved { position });
|
|
window.dispatch_event(WindowEvent::PointerPressed { position, button });
|
|
window.dispatch_event(WindowEvent::PointerReleased { position, button });
|
|
}
|
|
|
|
fn touch(window: &slint::Window, x: f32, y: f32, phase: TouchPhase) {
|
|
window.dispatch_event(WindowEvent::internal(InternalEvent::Touch {
|
|
id: 0,
|
|
position: LogicalPoint::new(x, y),
|
|
phase,
|
|
}));
|
|
}
|
|
|
|
/// Runs the open menu's first item, if a menu is open.
|
|
fn run_first_menu_item(window: &slint::Window) {
|
|
for key in [Key::DownArrow, Key::Return] {
|
|
window.dispatch_event(WindowEvent::KeyPressed { text: key.into() });
|
|
window.dispatch_event(WindowEvent::KeyReleased { text: key.into() });
|
|
}
|
|
}
|
|
|
|
// Slint's Android build turns a press on a menu's own area into a wait for a
|
|
// long press, so rows keep their touch area inside it: taps reach the row on
|
|
// every platform, and the row opens the menu itself.
|
|
#[test]
|
|
fn rows_take_taps_and_open_their_menus_on_right_click_or_hold() {
|
|
let clock = Rc::new(Cell::new(Duration::ZERO));
|
|
let window = MinimalSoftwareWindow::new(RepaintBufferType::NewBuffer);
|
|
slint::platform::set_platform(Box::new(Headless {
|
|
window: window.clone(),
|
|
clock: clock.clone(),
|
|
}))
|
|
.unwrap();
|
|
let ui = SidebarRowHarness::new().unwrap();
|
|
ui.show().unwrap();
|
|
let advance = |ms: u64| {
|
|
clock.set(clock.get() + Duration::from_millis(ms));
|
|
slint::platform::update_timers_and_animations();
|
|
};
|
|
|
|
click(ui.window(), 10.0, 14.0, PointerEventButton::Left);
|
|
assert_eq!(
|
|
ui.get_folder_toggles(),
|
|
1,
|
|
"the disclosure folds the folder"
|
|
);
|
|
click(ui.window(), 150.0, 14.0, PointerEventButton::Left);
|
|
assert_eq!(ui.get_selected_scope(), "Folder:5", "a click selects it");
|
|
click(ui.window(), 150.0, 60.0, PointerEventButton::Left);
|
|
assert_eq!(ui.get_account_toggles(), 1, "a click folds the account");
|
|
|
|
ui.set_selected_scope("".into());
|
|
touch(ui.window(), 150.0, 14.0, TouchPhase::Started);
|
|
advance(100);
|
|
touch(ui.window(), 150.0, 14.0, TouchPhase::Ended);
|
|
advance(600);
|
|
assert_eq!(ui.get_selected_scope(), "Folder:5", "a tap selects it");
|
|
run_first_menu_item(ui.window());
|
|
assert_eq!(ui.get_new_subfolders(), 0, "a tap opens no menu");
|
|
|
|
// Holding a finger still for half a second opens the menu, as on Android
|
|
// and iOS, and lifting it afterwards is not a tap.
|
|
ui.set_selected_scope("".into());
|
|
touch(ui.window(), 150.0, 14.0, TouchPhase::Started);
|
|
advance(600);
|
|
touch(ui.window(), 150.0, 14.0, TouchPhase::Ended);
|
|
assert_eq!(ui.get_selected_scope(), "");
|
|
run_first_menu_item(ui.window());
|
|
assert_eq!(ui.get_new_subfolders(), 1, "the hold opened the menu");
|
|
|
|
// A finger that moves is scrolling the sidebar.
|
|
touch(ui.window(), 150.0, 14.0, TouchPhase::Started);
|
|
advance(100);
|
|
touch(ui.window(), 150.0, 30.0, TouchPhase::Moved);
|
|
advance(600);
|
|
touch(ui.window(), 150.0, 30.0, TouchPhase::Ended);
|
|
run_first_menu_item(ui.window());
|
|
assert_eq!(ui.get_new_subfolders(), 1, "scrolling opens no menu");
|
|
|
|
click(ui.window(), 150.0, 60.0, PointerEventButton::Right);
|
|
run_first_menu_item(ui.window());
|
|
assert_eq!(ui.get_checks(), 1, "a right-click opens the account's menu");
|
|
assert_eq!(ui.get_account_toggles(), 1, "without folding the account");
|
|
}
|