Panics · P38 Interaction: handlers in new; resizes fit the limits now, stale drags and leaders do nothing
This commit is contained in:
1 parent
8db6cecd5a
commit
513ea50946
10 files changed
+278
-135
No files matched your search
@@ -246,12 +246,15 @@ pub fn focus(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
("strike", IconName::Strikethrough),
|
||||
];
|
||||
let toolbar = tools.into_iter().fold(
|
||||
RovingFocus::new("interaction-tools", Axis::Horizontal)
|
||||
.gap_0p5()
|
||||
.p_0p5()
|
||||
.rounded_lg()
|
||||
.border_1()
|
||||
.border_color(theme.colors.border),
|
||||
RovingFocus::new("interaction-tools", Axis::Horizontal, move |key, _, cx| {
|
||||
let key = key.clone();
|
||||
change(&press, cx, |focusing| focusing.tool = key)
|
||||
})
|
||||
.gap_0p5()
|
||||
.p_0p5()
|
||||
.rounded_lg()
|
||||
.border_1()
|
||||
.border_color(theme.colors.border),
|
||||
|bar, (key, icon)| {
|
||||
let on = tool.as_ref() == key;
|
||||
bar.item(
|
||||
@@ -308,10 +311,7 @@ pub fn focus(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
"RovingFocus",
|
||||
probe(
|
||||
"interaction-tools",
|
||||
toolbar.on_press(move |key, _, cx| {
|
||||
let key = key.clone();
|
||||
change(&press, cx, |focusing| focusing.tool = key)
|
||||
}),
|
||||
toolbar,
|
||||
),
|
||||
cx,
|
||||
))
|
||||
|
||||
@@ -162,15 +162,18 @@ pub fn shapes(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
let theme = cx.theme();
|
||||
let [resize, turn, pick, sort, double, press, long] = [(); 7].map(|_| state.clone());
|
||||
let tiles = (0..12).fold(
|
||||
SelectionArea::new("interaction-tiles")
|
||||
.w(px(300.))
|
||||
.flex()
|
||||
.flex_wrap()
|
||||
.gap(px(8.))
|
||||
.p_3()
|
||||
.rounded_lg()
|
||||
.border_1()
|
||||
.border_color(theme.colors.border),
|
||||
SelectionArea::new("interaction-tiles", move |keys, _, cx| {
|
||||
let keys = keys.to_vec();
|
||||
change(&pick, cx, |shapes| shapes.picked = keys)
|
||||
})
|
||||
.w(px(300.))
|
||||
.flex()
|
||||
.flex_wrap()
|
||||
.gap(px(8.))
|
||||
.p_3()
|
||||
.rounded_lg()
|
||||
.border_1()
|
||||
.border_color(theme.colors.border),
|
||||
|area, ix| {
|
||||
area.item(
|
||||
SharedString::from(format!("tile-{ix}")),
|
||||
@@ -204,8 +207,7 @@ pub fn shapes(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
"Resizable",
|
||||
probe(
|
||||
"interaction-resizable",
|
||||
Resizable::new("interaction-resizable", size_now, size(px(160.), px(96.)), size(px(360.), px(220.)))
|
||||
.on_resize(move |next, _, cx| change(&resize, cx, |shapes| shapes.size = next))
|
||||
Resizable::new("interaction-resizable", size_now, size(px(160.), px(96.)), size(px(360.), px(220.)), move |next, _, cx| change(&resize, cx, |shapes| shapes.size = next))
|
||||
.child(
|
||||
div()
|
||||
.size_full()
|
||||
@@ -225,8 +227,7 @@ pub fn shapes(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
"Rotatable",
|
||||
probe(
|
||||
"interaction-rotatable",
|
||||
Rotatable::new("interaction-rotatable", "icons/arrow-up.svg", px(120.), angle)
|
||||
.on_turn(move |next, _, cx| change(&turn, cx, |shapes| shapes.angle = next)),
|
||||
Rotatable::new("interaction-rotatable", "icons/arrow-up.svg", px(120.), angle, move |next, _, cx| change(&turn, cx, |shapes| shapes.angle = next)),
|
||||
),
|
||||
cx,
|
||||
)),
|
||||
@@ -237,10 +238,7 @@ pub fn shapes(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
"SelectionArea",
|
||||
probe(
|
||||
"interaction-tiles",
|
||||
tiles.selected(picked).on_change(move |keys, _, cx| {
|
||||
let keys = keys.to_vec();
|
||||
change(&pick, cx, |shapes| shapes.picked = keys)
|
||||
}),
|
||||
tiles.selected(picked),
|
||||
),
|
||||
cx,
|
||||
))
|
||||
|
||||
@@ -27,22 +27,30 @@ struct ResizeDrag {
|
||||
start: Size<Pixels>,
|
||||
}
|
||||
|
||||
/// The size after moving `side` by `delta` from `start`, kept within `min` and `max`.
|
||||
/// `size` within `min` and `max`.
|
||||
fn fit(size: Size<Pixels>, (min, max): (Size<Pixels>, Size<Pixels>)) -> Size<Pixels> {
|
||||
gpui::size(
|
||||
size.width.clamp(min.width, max.width),
|
||||
size.height.clamp(min.height, max.height),
|
||||
)
|
||||
}
|
||||
|
||||
/// The size after moving `side` by `delta` from `start`, both measures kept within the limits.
|
||||
fn resized(
|
||||
start: Size<Pixels>,
|
||||
side: Side,
|
||||
delta: Point<Pixels>,
|
||||
(min, max): (Size<Pixels>, Size<Pixels>),
|
||||
limits: (Size<Pixels>, Size<Pixels>),
|
||||
) -> Size<Pixels> {
|
||||
let width = match side {
|
||||
Side::Bottom => start.width,
|
||||
Side::Right | Side::Corner => (start.width + delta.x).clamp(min.width, max.width),
|
||||
Side::Right | Side::Corner => start.width + delta.x,
|
||||
};
|
||||
let height = match side {
|
||||
Side::Right => start.height,
|
||||
Side::Bottom | Side::Corner => (start.height + delta.y).clamp(min.height, max.height),
|
||||
Side::Bottom | Side::Corner => start.height + delta.y,
|
||||
};
|
||||
size(width, height)
|
||||
fit(size(width, height), limits)
|
||||
}
|
||||
|
||||
type OnResize = Rc<dyn Fn(Size<Pixels>, &mut Window, &mut App)>;
|
||||
@@ -54,7 +62,7 @@ pub struct Resizable {
|
||||
size: Size<Pixels>,
|
||||
limits: (Size<Pixels>, Size<Pixels>),
|
||||
children: SmallVec<[AnyElement; 1]>,
|
||||
on_resize: Option<OnResize>,
|
||||
on_resize: OnResize,
|
||||
}
|
||||
|
||||
impl Resizable {
|
||||
@@ -64,38 +72,24 @@ impl Resizable {
|
||||
size: Size<Pixels>,
|
||||
min: Size<Pixels>,
|
||||
max: Size<Pixels>,
|
||||
on_resize: impl Fn(Size<Pixels>, &mut Window, &mut App) + 'static,
|
||||
) -> Self {
|
||||
let id = id.into();
|
||||
assert!(
|
||||
min.width <= size.width && size.width <= max.width,
|
||||
"resizable {id:?}: width {:?} is not within {:?} and {:?}",
|
||||
size.width,
|
||||
min.width,
|
||||
max.width
|
||||
);
|
||||
assert!(
|
||||
min.height <= size.height && size.height <= max.height,
|
||||
"resizable {id:?}: height {:?} is not within {:?} and {:?}",
|
||||
size.height,
|
||||
min.height,
|
||||
max.height
|
||||
min.width <= max.width && min.height <= max.height,
|
||||
"resizable {id:?}: limits {min:?} to {max:?} run backwards"
|
||||
);
|
||||
if fit(size, (min, max)) != size {
|
||||
log::error!("resizable {id:?}: size {size:?} outside {min:?} to {max:?}; pegged");
|
||||
}
|
||||
Self {
|
||||
id,
|
||||
size,
|
||||
limits: (min, max),
|
||||
children: SmallVec::new(),
|
||||
on_resize: None,
|
||||
on_resize: Rc::new(on_resize),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_resize(
|
||||
mut self,
|
||||
handler: impl Fn(Size<Pixels>, &mut Window, &mut App) + 'static,
|
||||
) -> Self {
|
||||
self.on_resize = Some(Rc::new(handler));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ParentElement for Resizable {
|
||||
@@ -107,14 +101,17 @@ impl ParentElement for Resizable {
|
||||
impl RenderOnce for Resizable {
|
||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let id = self.id;
|
||||
let on_resize = self
|
||||
.on_resize
|
||||
.unwrap_or_else(|| panic!("resizable {id:?} has no on_resize"));
|
||||
let on_resize = self.on_resize;
|
||||
let limits = self.limits;
|
||||
let held = use_seeded((id.clone(), "size"), self.size, window, cx);
|
||||
let pressed =
|
||||
window.use_keyed_state((id.clone(), "press"), cx, |_, _| None::<Point<Pixels>>);
|
||||
let (owner, now) = (held.entity_id(), held.read(cx).value);
|
||||
let (owner, kept) = (held.entity_id(), held.read(cx).value);
|
||||
let now = fit(kept, limits);
|
||||
if now != kept {
|
||||
log::info!("resizable: {kept:?} fits the limits at {now:?}");
|
||||
held.update(cx, |held, _| held.value = now);
|
||||
}
|
||||
let report: OnResize = Rc::new(move |next, window, cx| {
|
||||
log::info!("resizable: {:?} by {:?}", next.width, next.height);
|
||||
held.update(cx, |held, cx| {
|
||||
@@ -153,7 +150,10 @@ impl RenderOnce for Resizable {
|
||||
return;
|
||||
}
|
||||
let at = event.event.position;
|
||||
let anchor = pressed.read(cx).expect("a resize starts from a press");
|
||||
let Some(anchor) = *pressed.read(cx) else {
|
||||
log::error!("resizable: a drag with no press behind it");
|
||||
return;
|
||||
};
|
||||
let next = resized(drag.start, drag.side, at - anchor, limits);
|
||||
if next != now {
|
||||
dragged(next, window, cx);
|
||||
|
||||
@@ -35,7 +35,7 @@ pub struct Rotatable {
|
||||
path: SharedString,
|
||||
side: Pixels,
|
||||
angle: f32,
|
||||
on_turn: Option<OnTurn>,
|
||||
on_turn: OnTurn,
|
||||
}
|
||||
|
||||
impl Rotatable {
|
||||
@@ -45,6 +45,7 @@ impl Rotatable {
|
||||
path: impl Into<SharedString>,
|
||||
side: Pixels,
|
||||
angle: f32,
|
||||
on_turn: impl Fn(f32, &mut Window, &mut App) + 'static,
|
||||
) -> Self {
|
||||
assert!(
|
||||
angle.is_finite(),
|
||||
@@ -55,23 +56,15 @@ impl Rotatable {
|
||||
path: path.into(),
|
||||
side,
|
||||
angle: angle.rem_euclid(360.0),
|
||||
on_turn: None,
|
||||
on_turn: Rc::new(on_turn),
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs with each new angle.
|
||||
pub fn on_turn(mut self, handler: impl Fn(f32, &mut Window, &mut App) + 'static) -> Self {
|
||||
self.on_turn = Some(Rc::new(handler));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for Rotatable {
|
||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let id = self.id;
|
||||
let on_turn = self
|
||||
.on_turn
|
||||
.unwrap_or_else(|| panic!("rotatable {id:?} has no on_turn"));
|
||||
let on_turn = self.on_turn;
|
||||
let held = use_seeded((id.clone(), "angle"), self.angle, window, cx);
|
||||
let (owner, angle) = (held.entity_id(), held.read(cx).value);
|
||||
let turn: OnTurn = Rc::new(move |next, window, cx| {
|
||||
|
||||
@@ -35,17 +35,21 @@ pub struct RovingFocus {
|
||||
axis: Axis,
|
||||
base: Div,
|
||||
items: Vec<(SharedString, AnyElement)>,
|
||||
on_press: Option<OnKey>,
|
||||
on_press: OnKey,
|
||||
}
|
||||
|
||||
impl RovingFocus {
|
||||
pub fn new(id: impl Into<ElementId>, axis: Axis) -> Self {
|
||||
pub fn new(
|
||||
id: impl Into<ElementId>,
|
||||
axis: Axis,
|
||||
on_press: impl Fn(&SharedString, &mut Window, &mut App) + 'static,
|
||||
) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
axis,
|
||||
base: div(),
|
||||
items: Vec::new(),
|
||||
on_press: None,
|
||||
on_press: Rc::new(on_press),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,15 +57,6 @@ impl RovingFocus {
|
||||
self.items.push((key.into(), content.into_any_element()));
|
||||
self
|
||||
}
|
||||
|
||||
/// Runs with the key of the item pressed.
|
||||
pub fn on_press(
|
||||
mut self,
|
||||
handler: impl Fn(&SharedString, &mut Window, &mut App) + 'static,
|
||||
) -> Self {
|
||||
self.on_press = Some(Rc::new(handler));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Styled for RovingFocus {
|
||||
@@ -73,9 +68,7 @@ impl Styled for RovingFocus {
|
||||
impl RenderOnce for RovingFocus {
|
||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let id = self.id;
|
||||
let on_press = self
|
||||
.on_press
|
||||
.unwrap_or_else(|| panic!("roving focus {id:?} has no on_press"));
|
||||
let on_press = self.on_press;
|
||||
assert!(!self.items.is_empty(), "roving focus {id:?} has no item");
|
||||
let stop = window.use_keyed_state((id.clone(), "stop"), cx, |_, _| 0usize);
|
||||
let count = self.items.len();
|
||||
|
||||
@@ -82,17 +82,20 @@ pub struct SelectionArea {
|
||||
base: Div,
|
||||
items: Vec<(SharedString, AnyElement)>,
|
||||
selected: Vec<SharedString>,
|
||||
on_change: Option<OnSelect>,
|
||||
on_change: OnSelect,
|
||||
}
|
||||
|
||||
impl SelectionArea {
|
||||
pub fn new(id: impl Into<ElementId>) -> Self {
|
||||
pub fn new(
|
||||
id: impl Into<ElementId>,
|
||||
on_change: impl Fn(&[SharedString], &mut Window, &mut App) + 'static,
|
||||
) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
base: div(),
|
||||
items: Vec::new(),
|
||||
selected: Vec::new(),
|
||||
on_change: None,
|
||||
on_change: Rc::new(on_change),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,15 +108,6 @@ impl SelectionArea {
|
||||
self.selected = keys.into_iter().map(Into::into).collect();
|
||||
self
|
||||
}
|
||||
|
||||
/// Gets the keys now selected, in the items' order.
|
||||
pub fn on_change(
|
||||
mut self,
|
||||
handler: impl Fn(&[SharedString], &mut Window, &mut App) + 'static,
|
||||
) -> Self {
|
||||
self.on_change = Some(Rc::new(handler));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Styled for SelectionArea {
|
||||
@@ -125,9 +119,7 @@ impl Styled for SelectionArea {
|
||||
impl RenderOnce for SelectionArea {
|
||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let id = self.id;
|
||||
let on_change = self
|
||||
.on_change
|
||||
.unwrap_or_else(|| panic!("selection area {id:?} has no on_change"));
|
||||
let on_change = self.on_change;
|
||||
let area = window.use_keyed_state((id.clone(), "area"), cx, |_, _| Area::default());
|
||||
let owner = area.entity_id();
|
||||
let focus = tab_stop((id.clone(), "focus").into(), true, window, cx);
|
||||
@@ -233,7 +225,10 @@ impl RenderOnce for SelectionArea {
|
||||
}
|
||||
let origin = event.bounds.origin;
|
||||
let at = event.event.position - origin;
|
||||
let press = moved.read(cx).press.expect("a band starts from a press");
|
||||
let Some(press) = moved.read(cx).press else {
|
||||
log::error!("selection area: a band with no press behind it");
|
||||
return;
|
||||
};
|
||||
let start = press - origin;
|
||||
let adding = match event.event.modifiers.platform || event.event.modifiers.shift {
|
||||
true => drag.adding.clone(),
|
||||
|
||||
@@ -64,7 +64,10 @@ impl RenderOnce for ScrollSync {
|
||||
let sync = canvas(
|
||||
move |_, _, _| {
|
||||
let Some(lead) = lead else { return };
|
||||
let place = synced[lead].offset();
|
||||
let Some(place) = synced.get(lead).map(|handle| handle.offset()) else {
|
||||
log::error!("scroll sync: leader pane {lead} of {} left", synced.len());
|
||||
return;
|
||||
};
|
||||
for (ix, handle) in synced.iter().enumerate() {
|
||||
let now = handle.offset();
|
||||
let next = along(now, place, axis);
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
use gpui::{
|
||||
AnyElement, Axis, Entity, IntoElement, Modifiers, MouseButton, ParentElement, Styled,
|
||||
TestAppContext, div, point, px, size,
|
||||
};
|
||||
|
||||
use super::{Bench, at, bench, picked, settle, tab, tap};
|
||||
use crate::interaction::{Resizable, ScrollSync};
|
||||
|
||||
/// A box whose greatest width drops to 205 once the angle turns.
|
||||
fn narrowing(bench: &Bench, _: Entity<Bench>) -> AnyElement {
|
||||
let max_width = if bench.angle == 0.0 { 300.0 } else { 205.0 };
|
||||
Resizable::new(
|
||||
"box",
|
||||
bench.size,
|
||||
size(px(100.0), px(80.0)),
|
||||
size(px(max_width), px(200.0)),
|
||||
|_, _, _| {},
|
||||
)
|
||||
.child(div().size_full())
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn width(cx: &mut gpui::VisualTestContext) -> gpui::Pixels {
|
||||
cx.debug_bounds("resizable-box")
|
||||
.expect("the box")
|
||||
.size
|
||||
.width
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn a_local_size_fits_limits_that_shrank(cx: &mut TestAppContext) {
|
||||
let (host, cx) = bench(narrowing, cx);
|
||||
tab(1, cx);
|
||||
tap("right", cx);
|
||||
assert_eq!(width(cx), px(208.0));
|
||||
host.update(cx, |bench, cx| {
|
||||
bench.angle = 1.0;
|
||||
cx.notify();
|
||||
});
|
||||
settle(cx);
|
||||
assert_eq!(width(cx), px(205.0));
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn a_new_owner_size_past_the_limits_replaces_a_local_one(cx: &mut TestAppContext) {
|
||||
let (host, cx) = bench(narrowing, cx);
|
||||
host.update(cx, |bench, cx| {
|
||||
bench.size.width = px(400.0);
|
||||
cx.notify();
|
||||
});
|
||||
settle(cx);
|
||||
assert_eq!(width(cx), px(300.0));
|
||||
tab(1, cx);
|
||||
tap("left", cx);
|
||||
assert_eq!(width(cx), px(292.0));
|
||||
host.update(cx, |bench, cx| {
|
||||
bench.size.width = px(500.0);
|
||||
cx.notify();
|
||||
});
|
||||
settle(cx);
|
||||
assert_eq!(width(cx), px(300.0));
|
||||
}
|
||||
|
||||
/// A box whose greatest height drops to 100 once the angle turns, and the heights it reports.
|
||||
fn lowering(bench: &Bench, owner: Entity<Bench>) -> AnyElement {
|
||||
let max_height = if bench.angle == 0.0 { 200.0 } else { 100.0 };
|
||||
Resizable::new(
|
||||
"box",
|
||||
bench.size,
|
||||
size(px(100.0), px(80.0)),
|
||||
size(px(300.0), px(max_height)),
|
||||
move |next, _, cx| {
|
||||
owner.update(cx, |bench, cx| {
|
||||
bench.size = next;
|
||||
bench
|
||||
.selected
|
||||
.push(format!("{}", f32::from(next.height)).into());
|
||||
cx.notify();
|
||||
})
|
||||
},
|
||||
)
|
||||
.child(div().size_full())
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn an_edge_drag_reports_both_measures_within_the_limits_now(cx: &mut TestAppContext) {
|
||||
let (host, cx) = bench(lowering, cx);
|
||||
let none = Modifiers::none();
|
||||
cx.simulate_mouse_down(at(217.0, 60.0), MouseButton::Left, none);
|
||||
for x in [230.0, 240.0] {
|
||||
cx.simulate_mouse_move(at(x, 60.0), Some(MouseButton::Left), none);
|
||||
settle(cx);
|
||||
}
|
||||
assert!(!picked(&host, cx).is_empty(), "the drag began");
|
||||
host.update(cx, |bench, cx| {
|
||||
bench.angle = 1.0;
|
||||
bench.selected.clear();
|
||||
cx.notify();
|
||||
});
|
||||
settle(cx);
|
||||
cx.simulate_mouse_move(at(250.0, 60.0), Some(MouseButton::Left), none);
|
||||
settle(cx);
|
||||
assert_eq!(picked(&host, cx).last().map(String::as_str), Some("100"));
|
||||
}
|
||||
|
||||
/// Panes that drop to two once the angle turns.
|
||||
fn synced(bench: &Bench, _: Entity<Bench>) -> AnyElement {
|
||||
let count = if bench.angle == 0.0 { 3 } else { 2 };
|
||||
(0..count)
|
||||
.fold(
|
||||
ScrollSync::new("sync", Axis::Vertical)
|
||||
.w(px(300.0))
|
||||
.h(px(200.0)),
|
||||
|sync, _| {
|
||||
sync.pane(
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.children((0..40).map(|_| div().h(px(30.0)))),
|
||||
)
|
||||
},
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn a_leader_pane_that_left_syncs_nothing(cx: &mut TestAppContext) {
|
||||
let (host, cx) = bench(synced, cx);
|
||||
cx.simulate_event(gpui::ScrollWheelEvent {
|
||||
position: at(270.0, 100.0),
|
||||
delta: gpui::ScrollDelta::Pixels(point(px(0.0), px(-300.0))),
|
||||
modifiers: Modifiers::none(),
|
||||
touch_phase: gpui::TouchPhase::Moved,
|
||||
});
|
||||
settle(cx);
|
||||
host.update(cx, |bench, cx| {
|
||||
bench.angle = 1.0;
|
||||
cx.notify();
|
||||
});
|
||||
settle(cx);
|
||||
}
|
||||
@@ -7,6 +7,8 @@ use gpui::{
|
||||
use super::{Resizable, Rotatable, RovingFocus, ScrollSync, SelectionArea};
|
||||
use crate::{forms, primitives::FocusNext, theme::Theme};
|
||||
|
||||
mod limits;
|
||||
|
||||
type Part = fn(&Bench, Entity<Bench>) -> AnyElement;
|
||||
|
||||
/// A view that shows one interaction part and holds what the owner decides: a size, an angle and a selection.
|
||||
@@ -88,13 +90,13 @@ fn resizable(bench: &Bench, owner: Entity<Bench>) -> AnyElement {
|
||||
bench.size,
|
||||
size(px(100.0), px(80.0)),
|
||||
size(px(300.0), px(200.0)),
|
||||
move |next, _, cx| {
|
||||
owner.update(cx, |bench, cx| {
|
||||
bench.size = next;
|
||||
cx.notify();
|
||||
})
|
||||
},
|
||||
)
|
||||
.on_resize(move |next, _, cx| {
|
||||
owner.update(cx, |bench, cx| {
|
||||
bench.size = next;
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
.child(div().size_full())
|
||||
.into_any_element()
|
||||
}
|
||||
@@ -135,6 +137,18 @@ fn a_box_resizes_from_its_grip_and_its_edge_within_its_limits(cx: &mut TestAppCo
|
||||
}
|
||||
|
||||
/// The grip and the edges' handles lie inside the box, the grip in its lower right corner.
|
||||
#[gpui::test]
|
||||
fn a_size_past_its_limits_pegs_and_draws(cx: &mut TestAppContext) {
|
||||
let (host, cx) = bench(resizable, cx);
|
||||
host.update(cx, |bench, cx| {
|
||||
bench.size = size(px(900.0), px(10.0));
|
||||
cx.notify();
|
||||
});
|
||||
settle(cx);
|
||||
let shown = cx.debug_bounds("resizable-box").expect("the box");
|
||||
assert_eq!(shown.size, size(px(300.0), px(80.0)));
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn the_grip_lies_inside_the_box(cx: &mut TestAppContext) {
|
||||
let (_, cx) = bench(resizable, cx);
|
||||
@@ -150,14 +164,19 @@ fn the_grip_lies_inside_the_box(cx: &mut TestAppContext) {
|
||||
}
|
||||
|
||||
fn rotatable(bench: &Bench, owner: Entity<Bench>) -> AnyElement {
|
||||
Rotatable::new("dial", "icons/arrow-up.svg", px(100.0), bench.angle)
|
||||
.on_turn(move |next, _, cx| {
|
||||
Rotatable::new(
|
||||
"dial",
|
||||
"icons/arrow-up.svg",
|
||||
px(100.0),
|
||||
bench.angle,
|
||||
move |next, _, cx| {
|
||||
owner.update(cx, |bench, cx| {
|
||||
bench.angle = next;
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
.into_any_element()
|
||||
},
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
/// The ring is the one stop; Left and Right turn it a notch, round the circle.
|
||||
@@ -206,21 +225,20 @@ fn area(bench: &Bench, owner: Entity<Bench>) -> AnyElement {
|
||||
["a", "b", "c"]
|
||||
.into_iter()
|
||||
.fold(
|
||||
SelectionArea::new("tiles")
|
||||
.flex()
|
||||
.gap(px(10.0))
|
||||
.p(px(20.0))
|
||||
.w(px(300.0)),
|
||||
SelectionArea::new("tiles", move |keys, _, cx| {
|
||||
let keys = keys.to_vec();
|
||||
owner.update(cx, |bench, cx| {
|
||||
bench.selected = keys;
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
.flex()
|
||||
.gap(px(10.0))
|
||||
.p(px(20.0))
|
||||
.w(px(300.0)),
|
||||
|area, key| area.item(key, div().size(px(40.0))),
|
||||
)
|
||||
.selected(bench.selected.clone())
|
||||
.on_change(move |keys, _, cx| {
|
||||
let keys = keys.to_vec();
|
||||
owner.update(cx, |bench, cx| {
|
||||
bench.selected = keys;
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
@@ -315,16 +333,16 @@ fn roving(bench: &Bench, owner: Entity<Bench>) -> AnyElement {
|
||||
let _ = bench;
|
||||
["a", "b", "c"]
|
||||
.into_iter()
|
||||
.fold(RovingFocus::new("tools", Axis::Horizontal), |group, key| {
|
||||
group.item(key, div().size(px(24.0)))
|
||||
})
|
||||
.on_press(move |key, _, cx| {
|
||||
let key = key.clone();
|
||||
owner.update(cx, |bench, cx| {
|
||||
bench.selected.push(key);
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
.fold(
|
||||
RovingFocus::new("tools", Axis::Horizontal, move |key, _, cx| {
|
||||
let key = key.clone();
|
||||
owner.update(cx, |bench, cx| {
|
||||
bench.selected.push(key);
|
||||
cx.notify();
|
||||
})
|
||||
}),
|
||||
|group, key| group.item(key, div().size(px(24.0))),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
+2
-1
@@ -52,7 +52,7 @@ Each item reads every site in its folders, fixes what the rules name, updates ca
|
||||
- [x] P35 Settings — `src/settings`
|
||||
- [x] P36 Account — `src/account`
|
||||
- [x] P37 Onboarding — `src/onboarding`
|
||||
- [ ] P38 Interaction — `src/interaction`
|
||||
- [x] P38 Interaction — `src/interaction`
|
||||
- [ ] P39 Theme — `src/theme`
|
||||
- [ ] P40 i18n & a11y — `src/i18n`
|
||||
- [ ] P41 Maps — `src/maps`
|
||||
@@ -103,3 +103,4 @@ One line per item: what changed, what stayed and why, review rounds.
|
||||
- P35: eighteen settings components take their handlers in `new`; a section or syntax theme not offered marks none; a font size or default outside its range pegs. Kept: options offered by construction, the proxy's Apply behind its read, drops that bring a file. Review: 1 round.
|
||||
- P36: seventeen account components take their handlers in `new`, the email step's `on_send` with them; a busy provider, a current account or workspace, and a role no longer listed mark none and log; an invitation waits for its role to be offered; an expiry year that overflows reads as unread. Kept: quotas, providers, one current session, roles to invite as, the card behind Save. Review: 2 rounds.
|
||||
- P37: seven onboarding components take their handlers in `new`, OnboardingWizard's `on_step` and `on_finish` among them; a help article no longer listed opens none; a wizard step past its last says so; a support topic no longer offered counts as none and holds Send. Kept: tasks, steps, releases and topics present, sentiments by construction, Send behind a pick. Review: 2 rounds.
|
||||
- P38: Resizable, Rotatable, RovingFocus and SelectionArea take their handlers in `new`; a resize or band whose press state is gone moves nothing; Resizable keeps the owner's raw size as its seed and fits what it draws and reports to this frame's limits; ScrollSync's leader that left syncs nothing. Kept: finite angles, items and panes present. Review: 2 rounds.
|
||||
Reference in new issue
Block a user