add segmented control
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use gpui::{
|
||||
Context, IntoElement, MouseButton, ParentElement as _, Render, Styled as _, TestAppContext,
|
||||
VisualTestContext, Window, div, point, px,
|
||||
};
|
||||
use gpui_native_core::segmented::Segmented;
|
||||
use gpui_native_theme::Theme;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum Filter {
|
||||
All,
|
||||
Popular,
|
||||
Recent,
|
||||
}
|
||||
|
||||
const FILTERS: [(Filter, &str); 3] = [
|
||||
(Filter::All, "All"),
|
||||
(Filter::Popular, "Popular"),
|
||||
(Filter::Recent, "Recent"),
|
||||
];
|
||||
|
||||
type Changes = Rc<RefCell<Vec<Filter>>>;
|
||||
|
||||
struct Harness {
|
||||
filter: Filter,
|
||||
changes: Changes,
|
||||
}
|
||||
|
||||
impl Render for Harness {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let changes = self.changes.clone();
|
||||
let view = cx.entity();
|
||||
div().size_full().p_4().child(
|
||||
Segmented::new("filter", self.filter)
|
||||
.options(FILTERS)
|
||||
.on_change(move |next, _window, cx| {
|
||||
changes.borrow_mut().push(next);
|
||||
view.update(cx, |harness, cx| {
|
||||
harness.filter = next;
|
||||
cx.notify();
|
||||
});
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn filter_window() -> (TestAppContext, VisualTestContext, Changes) {
|
||||
let mut cx = TestAppContext::single();
|
||||
cx.update(Theme::init);
|
||||
let changes = Rc::new(RefCell::new(Vec::new()));
|
||||
let harness_changes = changes.clone();
|
||||
let window = cx.add_window(move |_, _| Harness {
|
||||
filter: Filter::All,
|
||||
changes: harness_changes,
|
||||
});
|
||||
let mut visual = VisualTestContext::from_window(window.into(), &cx);
|
||||
visual.update(|window, cx| {
|
||||
window.draw(cx).clear(cx);
|
||||
});
|
||||
(cx, visual, changes)
|
||||
}
|
||||
|
||||
fn click(visual: &mut VisualTestContext, position: gpui::Point<gpui::Pixels>) {
|
||||
visual.simulate_mouse_down(position, MouseButton::Left, gpui::Modifiers::default());
|
||||
visual.simulate_mouse_up(position, MouseButton::Left, gpui::Modifiers::default());
|
||||
}
|
||||
|
||||
/// The strip hugs its labels in the `p_4` band: the three segments span
|
||||
/// roughly `18..142` horizontally and `17.6..33.9` vertically (macOS's 13 pt
|
||||
/// root font), so clicks land near `32` (All), `70` (Popular), and `118`
|
||||
/// (Recent).
|
||||
#[test]
|
||||
fn clicking_a_segment_reports_it() {
|
||||
let (_cx, mut visual, changes) = filter_window();
|
||||
|
||||
click(&mut visual, point(px(70.), px(27.)));
|
||||
assert_eq!(*changes.borrow(), vec![Filter::Popular]);
|
||||
|
||||
click(&mut visual, point(px(118.), px(27.)));
|
||||
assert_eq!(*changes.borrow(), vec![Filter::Popular, Filter::Recent]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn re_clicking_the_selected_segment_reports_nothing() {
|
||||
let (_cx, mut visual, changes) = filter_window();
|
||||
|
||||
click(&mut visual, point(px(70.), px(27.)));
|
||||
assert_eq!(*changes.borrow(), vec![Filter::Popular]);
|
||||
|
||||
click(&mut visual, point(px(70.), px(27.)));
|
||||
assert_eq!(*changes.borrow(), vec![Filter::Popular]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arrows_move_the_selection_when_the_strip_has_focus() {
|
||||
let (_cx, mut visual, changes) = filter_window();
|
||||
|
||||
// Clicking a segment hands the strip the keyboard, like the list.
|
||||
click(&mut visual, point(px(32.), px(27.)));
|
||||
visual.simulate_keystrokes("right");
|
||||
assert_eq!(*changes.borrow(), vec![Filter::Popular]);
|
||||
|
||||
visual.simulate_keystrokes("right");
|
||||
assert_eq!(*changes.borrow(), vec![Filter::Popular, Filter::Recent]);
|
||||
|
||||
// The strip never wraps: right on the last segment reports nothing.
|
||||
visual.simulate_keystrokes("right");
|
||||
assert_eq!(*changes.borrow(), vec![Filter::Popular, Filter::Recent]);
|
||||
|
||||
// Home and end jump to the ends.
|
||||
visual.simulate_keystrokes("home");
|
||||
assert_eq!(
|
||||
*changes.borrow(),
|
||||
vec![Filter::Popular, Filter::Recent, Filter::All]
|
||||
);
|
||||
|
||||
visual.simulate_keystrokes("end");
|
||||
assert_eq!(
|
||||
*changes.borrow(),
|
||||
vec![Filter::Popular, Filter::Recent, Filter::All, Filter::Recent]
|
||||
);
|
||||
}
|
||||
|
||||
/// The selected plate exists after a choice and settles on the chosen
|
||||
/// segment's slot; without it the active background disappears entirely.
|
||||
#[test]
|
||||
fn the_plate_settles_on_the_chosen_segment() {
|
||||
let (_cx, mut visual, changes) = filter_window();
|
||||
|
||||
click(&mut visual, point(px(70.), px(27.)));
|
||||
assert_eq!(*changes.borrow(), vec![Filter::Popular]);
|
||||
|
||||
// Let the spring settle in real time (it steps on Instant::now()), then
|
||||
// check the plate landed in Popular's slot (~58..124).
|
||||
std::thread::sleep(std::time::Duration::from_millis(600));
|
||||
visual.update(|window, cx| {
|
||||
window.draw(cx).clear(cx);
|
||||
});
|
||||
let plate = visual
|
||||
.debug_bounds("segmented-plate")
|
||||
.expect("plate missing");
|
||||
assert!(
|
||||
plate.left() >= px(44.) && plate.left() <= px(94.),
|
||||
"plate settled at x {:?}",
|
||||
plate.origin
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user