Port · Build: Zed's gpui at 1a28cff
gpui and gpui_platform now come from Zed's repo by git at 1a28cff, the code gpui-pre 0.3.7 republishes, so the gallery can later build for the browser. gpui takes stacker, as 0.2.2 always did, and gpui_platform takes font-kit, without which macOS draws no text; runtime_shaders moves to gpui_platform. The API moved: focus, blur and Tab steps take cx, Corner is Anchor, shaped lines paint with an alignment, defer_draw takes a content mask, the inspector registers a factory, async updates return their value, and wrapped lines are shared, not cloned. Updates through a window context go through the window, so a closed window still ends them as it did. Where the new gpui behaves differently, Ely keeps its old intent. Holds and reveals under a resting pointer survive typing through gpui's input-modality-independent hover; previews the keyboard also moves keep the default, and the calendar's leave ignores a key press. Enter reaches a button inside the code editor, since gpui now arms a key click on the press. The event logger skips a lone modifier. A video and an image grid keep their shape in a flex column under taffy 0.13, as a picture does. AGENTS.md records the new stack and the rules this port changes; the rest of the old gpui and taffy rules are re-tested in T44c.
This commit is contained in:
1 parent
25b8a6f22c
commit
4c3ed1bbb9
199 files changed
+2513
-2643
No files matched your search
@@ -5,7 +5,7 @@ Ely GPUI Component. A component library for GPUI, in light and dark.
|
||||
## Stack
|
||||
|
||||
- Rust 1.95, edition 2024. One crate: `ely-gpui-component`.
|
||||
- gpui 0.2.2 from crates.io. Feature `runtime_shaders` is on by default: machines without Xcode lack the Metal compiler.
|
||||
- gpui and gpui_platform from Zed's repo by git at 1a28cff, the code gpui-pre 0.3.7 republishes. gpui takes `stacker`, which keeps deep layouts off the stack's end; gpui_platform takes `font-kit`, without which macOS draws no text. Feature `runtime_shaders`, on by default, turns on gpui_platform's: machines without Xcode lack the Metal compiler.
|
||||
- Assets, embedded with `rust-embed`: Lucide 1.48.0 icons (ISC), Inter 4.1 (regular, medium, semibold, italic) and JetBrains Mono 2.304 (OFL).
|
||||
- Catalog data: `emojis` (Unicode emoji), `isolang` (ISO 639, native names), `isocountry` (ISO 3166), `iso_currency` (ISO 4217, also minor units for money).
|
||||
- Codes: `qrcode` (QR) and `barcoders` (Code 128, EAN-13), both MIT OR Apache-2.0, default features off. `rqrr` 0.11 ((MIT OR Apache-2.0) AND ISC, default features off) reads QR codes from the host's frames.
|
||||
@@ -53,6 +53,7 @@ Ely GPUI Component. A component library for GPUI, in light and dark.
|
||||
- `hover()` needs an element id.
|
||||
- gpui applies hover and group-hover styles once an element has a hitbox, in prepaint and paint, but text takes its color at layout, so a hover cannot recolor text. What shows on hover rests `invisible()` and turns `visible()` through `group_hover` (`chat::ConversationItem`, `messaging::ChatMessage`).
|
||||
- gpui 0.2.2 panics on a deferred draw inside another. Whatever draws over the page goes through `primitives::raise`: at the top it defers; inside something raised it lays out on its own and draws last there, so a list opened in a dialog lies over the dialog. A raise takes its owner's id and draws under it, so its keyed state and focus hold while other raises come and go. `scripts/check.sh` refuses gpui's `deferred` anywhere else.
|
||||
- gpui hides hover after a key press until the mouse moves. A hover that holds or shows something (the toast stack, a carousel, tooltips, hover cards, charts, scroll activity) sets `HoverListenerMode::InputModalityIndependent`, so typing under a resting pointer keeps it. A hover preview the keyboard also moves keeps the default, and its leave ignores a key press through `last_input_was_keyboard` (`forms::Calendar`).
|
||||
- `occlude()` blocks the pointer for everything painted before it, its own ancestors too. Put it on the outermost box that should stop the pointer, as the toast stack does, or the ancestors' hover never fires.
|
||||
- A drag that pans, turns or slides goes through gpui's drag, whose moves reach the owner in or out of its box; `on_mouse_move` stops at the box's edge. gpui sends a drag's moves to every listener of its type, so each payload carries its owner's `EntityId` and handlers check it.
|
||||
- gpui 0.2.2 paints pictures without a transform, so a picture turns by its pixels: `media::turn` turns decoded frames by quarter turns off the main thread, an asset keyed by the picture and the turn. An svg turns to any angle through its `Transformation`, so a control that turns shows one (`interaction::Rotatable`).
|
||||
@@ -80,7 +81,7 @@ Ely GPUI Component. A component library for GPUI, in light and dark.
|
||||
- gpui focuses the nearest focusable ancestor on mouse down, and apps wrap everything in a `FocusScope`. A press that must leave focus alone calls `prevent_default`, as buttons and context regions do.
|
||||
- gpui runs the click of every pressed element under the pointer, a row's with its button's. A pressable row that holds buttons stops their press at their box on mouse down (`files::transfer`), as `overlays::media_button` stops its own.
|
||||
- gpui draws a window's first frame inside `open_window`. State the first frame reads, such as `cx.set_menus`, is set before it opens.
|
||||
- gpui clicks a focused element when Enter or Space is released. An overlay that hands focus back picks on release too; a pick on press returns focus first, and the release clicks the opener again. An edit in place that hands focus back keeps its text on Enter's release (`forms::InlineEdit`). The press itself bubbles, so a container that takes Space leaves a focused button its own: `overlays::media_button` stops it, and a button in a `SelectableList` row stops Space and Enter at its box (`mail::MailItem`'s star, `messaging::PinnedMessages`' Unpin).
|
||||
- gpui clicks a focused element when Enter or Space is released, and only if the press reached it and focus stayed there. An overlay that hands focus back picks on release too. An edit in place that hands focus back keeps its text on Enter's release (`forms::InlineEdit`). The press itself bubbles, so a container that takes Space leaves a focused button its own: `overlays::media_button` stops it, and a button in a `SelectableList` row stops Space and Enter at its box (`mail::MailItem`'s star, `messaging::PinnedMessages`' Unpin). A key context that binds a key its children use acts only while its own handle is focused and propagates otherwise, or a focused child never arms (`editor::CodeEditor`'s Newline).
|
||||
- A focus handle a component hands back lives across all its modes: take it with `tab_stop` before any early return, or keyed state drops it while the other mode draws and the handed-back focus lands nowhere. A thread's message keeps one handle folded and open, a Tab stop only while folded (`mail::MailThreadView`).
|
||||
- A button whose words and action change with a mode keeps one handle through `Button::focus_handle`, and the component moves focus to it or back to its field as the mode turns (`project::TimeTracker`).
|
||||
- A button that waits on the owner shows it with `loading`, which takes no press and stays a Tab stop, so focus holds through the wait; `disabled` is for what is not ready, and a disabled button is no stop (`onboarding::ContactSupport`).
|
||||
@@ -136,6 +137,7 @@ Ely GPUI Component. A component library for GPUI, in light and dark.
|
||||
- A picture drawn in code reaches gpui as a `RenderImage`. A host that replaces one frees the old through `App::drop_image`, or every frame stays in the sprite atlas (the gallery's model).
|
||||
- `img()` keeps loading state, and moves an animated picture's frames, only with an id. Content masks are rectangles, so rounded corners survive only when the image fills its box without cropping.
|
||||
- gpui reads a `&str` picture source as a web address. A file goes through `Path` (`documents::source` in the crate, `Path::new` in the gallery).
|
||||
- Taffy 0.13 sizes a flex column by a ratio box's width before its cap, so chat's `shaped` pictures sit in a block inside such a column (`chat::ImageMessage`, `VideoMessage`, `ImageGrid`).
|
||||
- Taffy 0.9's grid rows overgrow tiles that keep a ratio. Tiles in a picture's shape sit in flex rows of `flex_1` cells (`generative::GenerationGrid`). A cell's padding counts in its flex base, so a row of fewer cells gives each more width; cells that match across rows keep their inset on a box inside (`messaging::VideoCallGrid`).
|
||||
- Rise reads green and fall red unless a component's `red_up` swaps them, as markets in East Asia read. `finance::quotes::moves` picks the pair.
|
||||
- Charts lay out in `f32` pixels of their own box, tested without gpui, and paint through `charts::paint::at`: a canvas's paint gets window coordinates, and `with_element_offset` works only in prepaint. Their sizes come from `theme.chart()`.
|
||||
@@ -164,7 +166,7 @@ Ely GPUI Component. A component library for GPUI, in light and dark.
|
||||
- A component built on `lists::Tree` fills its box; the host gives it a height. A fixed-width column beside a `flex_1` one takes `flex_none`, or it shrinks.
|
||||
- Editor rows read their text through `CodeEditor::row_text`: a multi-line ghost cuts its line, and the rest follows the ghost's last line.
|
||||
- gpui styles a drag-over only on an element with its own hitbox. A veil that shows while files hover takes the drop itself (`chat::DragDropOverlay`).
|
||||
- gpui 0.2.2 never clears `debug_bounds`: a test sees an element appear, not leave.
|
||||
- gpui clears `debug_bounds` each frame: a test sees an element leave as well as appear.
|
||||
- The system time zone comes from `typography::format::system_zone`, which fails loud; jiff's `TimeZone::system()` falls back to UTC in silence.
|
||||
- A Select with nothing to pick stays shut: disabled, it shows its placeholder; open, it fails (`media::DeviceSelector` says no device was found).
|
||||
- A list opens with its cursor on the current choice: Select on the chosen row, Combobox on its value, MultiSelect on the first one ticked, SelectableList on its first selected row. A selection the owner makes, not the echo of the list's own pick, moves SelectableList's cursor there.
|
||||
@@ -207,7 +209,7 @@ Ely GPUI Component. A component library for GPUI, in light and dark.
|
||||
|
||||
- gpui's inspector (debug builds) runs no mouse listener while picking and holds a pick on a press without a redraw, so Ely's panel requests a frame each render while picking (`tooling::install_inspector`). Its panel scrolls, so the box model it draws over the page goes through `primitives::raise`, which paints unclipped. The panel is a second root, out of the app's scope, so it owns Tab through a `FocusScope` of its own.
|
||||
- A tool that measures redraws cannot move: any animation redraws the view it counts (`tooling::RenderCounter`). `AnyView::cached` renders again only on its own notify or a window refresh. A meter that counts frames keeps its view drawing while shown (`tooling::FpsMeter`).
|
||||
- A logger of input lays its canvas before what it holds, so its capture listeners run first and see what a child stops. Key downs come through `App::intercept_keystrokes`, which runs before bindings, and a key context on its box tells it focus is inside (`tooling::EventLogger`).
|
||||
- A logger of input lays its canvas before what it holds, so its capture listeners run first and see what a child stops. Key downs come through `App::intercept_keystrokes`, which runs before bindings, and a key context on its box tells it focus is inside (`tooling::EventLogger`). It also hears a lone modifier's release as a keystroke, which the logger skips.
|
||||
|
||||
## Decisions
|
||||
|
||||
|
||||
Generated
+1593
-1984
File diff suppressed because it is too large.
Load diff
+3
-2
@@ -10,7 +10,7 @@ publish = false
|
||||
|
||||
[features]
|
||||
default = ["runtime_shaders"]
|
||||
runtime_shaders = ["gpui/runtime_shaders"]
|
||||
runtime_shaders = ["gpui_platform/runtime_shaders"]
|
||||
test-support = ["gpui/test-support"]
|
||||
|
||||
[dependencies]
|
||||
@@ -19,7 +19,8 @@ anyhow = "1"
|
||||
barcoders = { version = "2", default-features = false, features = ["std"] }
|
||||
emojis = "0.9"
|
||||
futures = "0.3"
|
||||
gpui = "0.2.2"
|
||||
gpui = { git = "https://github.com/zed-industries/zed", rev = "1a28cff4b409169bac058bca40dfbfeb7621d19b", default-features = false, features = ["stacker"] }
|
||||
gpui_platform = { git = "https://github.com/zed-industries/zed", rev = "1a28cff4b409169bac058bca40dfbfeb7621d19b", features = ["font-kit"] }
|
||||
image = { version = "0.25", default-features = false }
|
||||
iso_currency = { version = "0.7", features = ["iterator"] }
|
||||
isocountry = "0.3"
|
||||
|
||||
@@ -26,7 +26,7 @@ pub fn run(
|
||||
Theme::update(cx, |theme| theme.reduced_motion = true);
|
||||
cx.spawn(async move |cx| {
|
||||
let outcome = shoot_all(window, &dir, only, scripted, cx).await;
|
||||
if let Err(error) = outcome.and_then(|()| cx.update(|cx| cx.quit())) {
|
||||
if let Err(error) = outcome.map(|()| cx.update(|cx| cx.quit())) {
|
||||
log::error!("capture failed: {error:#}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
@@ -22,8 +22,8 @@ use std::path::PathBuf;
|
||||
use anyhow::{Context as _, Result, bail};
|
||||
use ely_gpui_component::Assets;
|
||||
use gpui::{
|
||||
App, AppContext, Application, Bounds, KeyBinding, Menu, MenuItem, TitlebarOptions,
|
||||
WindowBounds, WindowHandle, WindowOptions, actions, point, px, size,
|
||||
App, AppContext, Bounds, KeyBinding, Menu, MenuItem, TitlebarOptions, WindowBounds,
|
||||
WindowHandle, WindowOptions, actions, point, px, size,
|
||||
};
|
||||
|
||||
actions!(gallery, [Quit, About, LightMode, DarkMode, NewWindow]);
|
||||
@@ -38,6 +38,7 @@ fn menus(cx: &mut App) {
|
||||
MenuItem::separator(),
|
||||
MenuItem::action("Quit Ely", Quit),
|
||||
],
|
||||
disabled: false,
|
||||
},
|
||||
Menu {
|
||||
name: "View".into(),
|
||||
@@ -45,6 +46,7 @@ fn menus(cx: &mut App) {
|
||||
MenuItem::action("Light", LightMode),
|
||||
MenuItem::action("Dark", DarkMode),
|
||||
],
|
||||
disabled: false,
|
||||
},
|
||||
]);
|
||||
cx.set_dock_menu(vec![MenuItem::action("New Window", NewWindow)]);
|
||||
@@ -103,7 +105,7 @@ fn parse_args() -> Result<Args> {
|
||||
fn main() -> Result<()> {
|
||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
|
||||
let args = parse_args()?;
|
||||
Application::new()
|
||||
gpui_platform::application()
|
||||
.with_assets(Assets)
|
||||
.run(move |cx: &mut App| {
|
||||
ely_gpui_component::init(cx);
|
||||
@@ -123,6 +125,8 @@ fn main() -> Result<()> {
|
||||
traffic_light_position: Some(point(px(16.0), px(18.0))),
|
||||
}),
|
||||
window_min_size: Some(size(px(880.0), px(560.0))),
|
||||
// Draws every frame while a demo window is key, as gpui 0.2.2 did.
|
||||
inactive_frame_interval: None,
|
||||
..Default::default()
|
||||
};
|
||||
let window = cx
|
||||
|
||||
@@ -105,6 +105,7 @@ pub fn styles(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
offset: point(px(shadow.x), px(shadow.y)),
|
||||
blur_radius: px(shadow.blur),
|
||||
spread_radius: px(shadow.spread),
|
||||
inset: false,
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
|
||||
@@ -185,7 +185,7 @@ pub fn realtime(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
cx.background_executor()
|
||||
.timer(Duration::from_millis(900))
|
||||
.await;
|
||||
let pushed = cx.update(|cx| {
|
||||
cx.update(|cx| {
|
||||
stream.update(cx, |(values, pushed), cx| {
|
||||
let last = values.last().copied().unwrap_or(60.0);
|
||||
values.push((last + (rng() - 0.5) * 10.0).clamp(20.0, 100.0));
|
||||
@@ -194,9 +194,6 @@ pub fn realtime(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
cx.notify();
|
||||
})
|
||||
});
|
||||
if pushed.is_err() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
@@ -199,7 +199,7 @@ pub fn streaming(window: &mut Window, cx: &mut App) -> impl IntoElement + use<>
|
||||
cx.background_executor().timer(Duration::from_millis(600)).await;
|
||||
loop {
|
||||
cx.background_executor().timer(Duration::from_millis(45)).await;
|
||||
let more = feeder.update(cx, |stream, cx| {
|
||||
let more = cx.update(|_, cx| feeder.update(cx, |stream, cx| {
|
||||
let next = (stream.arrived + 7).min(ANSWER.len());
|
||||
stream.arrived = (next..=ANSWER.len())
|
||||
.find(|at| ANSWER.is_char_boundary(*at))
|
||||
@@ -207,8 +207,8 @@ pub fn streaming(window: &mut Window, cx: &mut App) -> impl IntoElement + use<>
|
||||
stream.live = stream.arrived < ANSWER.len();
|
||||
cx.notify();
|
||||
stream.live
|
||||
});
|
||||
if !matches!(more, Ok(true)) {
|
||||
}));
|
||||
if !matches!(more.and_then(|inner| inner), Ok(true)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,8 +221,7 @@ pub fn playground(window: &mut Window, cx: &mut App) -> impl IntoElement + use<>
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.ok();
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
});
|
||||
|
||||
@@ -117,8 +117,7 @@ pub fn panel(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
settings.enhancing = false;
|
||||
settings.suggestion = Some(text.into());
|
||||
})
|
||||
})
|
||||
.ok();
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
})
|
||||
|
||||
@@ -298,7 +298,7 @@ pub fn focus(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
.on_click(move |_, window, cx| {
|
||||
change(&show, cx, |focusing| focusing.field = !focusing.field);
|
||||
let focus = focus.clone();
|
||||
window.defer(cx, move |window, _| window.focus(&focus));
|
||||
window.defer(cx, move |window, cx| window.focus(&focus, cx));
|
||||
}),
|
||||
)
|
||||
.when(shown, |column| column.child(Input::new(&field))),
|
||||
|
||||
@@ -67,7 +67,7 @@ pub fn infinite(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
let more = more.clone();
|
||||
cx.spawn(async move |cx| {
|
||||
cx.background_executor().timer(Duration::from_millis(700)).await;
|
||||
cx.update(|cx| set(&more, (rows + PAGE, false), cx)).ok();
|
||||
cx.update(|cx| set(&more, (rows + PAGE, false), cx));
|
||||
})
|
||||
.detach();
|
||||
}),
|
||||
|
||||
@@ -144,8 +144,7 @@ pub fn tree(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
items.push(item("logo", "logo.svg", Some(&key), false));
|
||||
items.push(item("inter", "Inter.ttf", Some(&key), false));
|
||||
})
|
||||
})
|
||||
.ok();
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
})
|
||||
|
||||
@@ -4,7 +4,9 @@ use ely_gpui_component::media::{
|
||||
AudioPlayer, AudioSpectrum, AudioWaveform, MediaControls, PlaybackSpeedControl, Playlist,
|
||||
Repeat, Track, VolumeControl,
|
||||
};
|
||||
use gpui::{App, Entity, IntoElement, ParentElement, SharedString, Styled, Window, div, px};
|
||||
use gpui::{
|
||||
App, Entity, IntoElement, ParentElement, SharedString, Styled, TaskExt as _, Window, div, px,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
probe::probe,
|
||||
@@ -98,19 +100,21 @@ fn tick(state: &Entity<Listening>, window: &mut Window, cx: &mut App) {
|
||||
.spawn(cx, async move |cx| {
|
||||
loop {
|
||||
cx.background_executor().timer(TICK).await;
|
||||
let going = state.update(cx, |now, cx| {
|
||||
now.at = (now.at + TICK.mul_f32(now.speed)).min(now.length());
|
||||
if now.at == now.length() {
|
||||
match now.repeat {
|
||||
Repeat::One => now.at = Duration::ZERO,
|
||||
Repeat::All => now.skip(1),
|
||||
Repeat::Off if now.song + 1 < SONGS.len() => now.skip(1),
|
||||
Repeat::Off => now.playing = false,
|
||||
let going = cx.update(|_, cx| {
|
||||
state.update(cx, |now, cx| {
|
||||
now.at = (now.at + TICK.mul_f32(now.speed)).min(now.length());
|
||||
if now.at == now.length() {
|
||||
match now.repeat {
|
||||
Repeat::One => now.at = Duration::ZERO,
|
||||
Repeat::All => now.skip(1),
|
||||
Repeat::Off if now.song + 1 < SONGS.len() => now.skip(1),
|
||||
Repeat::Off => now.playing = false,
|
||||
}
|
||||
}
|
||||
}
|
||||
now.ticking = now.playing;
|
||||
cx.notify();
|
||||
now.playing
|
||||
now.ticking = now.playing;
|
||||
cx.notify();
|
||||
now.playing
|
||||
})
|
||||
})?;
|
||||
if !going {
|
||||
return anyhow::Ok(());
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use std::{path::Path, time::Duration};
|
||||
|
||||
use ely_gpui_component::media::{Cue, Scrubber, SubtitleEditor, VideoPlayer, VideoThumbnailStrip};
|
||||
use gpui::{App, Entity, IntoElement, ParentElement, SharedString, Styled, Window, div, px};
|
||||
use gpui::{
|
||||
App, Entity, IntoElement, ParentElement, SharedString, Styled, TaskExt as _, Window, div, px,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
probe::probe,
|
||||
@@ -61,13 +63,15 @@ fn tick(state: &Entity<Playback>, window: &mut Window, cx: &mut App) {
|
||||
.spawn(cx, async move |cx| {
|
||||
loop {
|
||||
cx.background_executor().timer(TICK).await;
|
||||
let going = state.update(cx, |now, cx| {
|
||||
let step = TICK.mul_f32(now.speed);
|
||||
now.at = (now.at + step).min(LENGTH);
|
||||
now.playing &= now.at < LENGTH;
|
||||
now.ticking = now.playing;
|
||||
cx.notify();
|
||||
now.playing
|
||||
let going = cx.update(|_, cx| {
|
||||
state.update(cx, |now, cx| {
|
||||
let step = TICK.mul_f32(now.speed);
|
||||
now.at = (now.at + step).min(LENGTH);
|
||||
now.playing &= now.at < LENGTH;
|
||||
now.ticking = now.playing;
|
||||
cx.notify();
|
||||
now.playing
|
||||
})
|
||||
})?;
|
||||
if !going {
|
||||
return anyhow::Ok(());
|
||||
|
||||
@@ -24,9 +24,7 @@ pub fn later<T: 'static>(state: &Entity<T>, value: T, wait: Duration, cx: &mut A
|
||||
let state = state.clone();
|
||||
cx.spawn(async move |cx| {
|
||||
cx.background_executor().timer(wait).await;
|
||||
if let Err(error) = cx.update(|cx| set(&state, value, cx)) {
|
||||
log::error!("gallery: the demo closed before its answer: {error:#}");
|
||||
}
|
||||
cx.update(|cx| set(&state, value, cx));
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
@@ -23,11 +23,13 @@ pub fn animated_number(window: &mut Window, cx: &mut App) -> impl IntoElement +
|
||||
_task: cx.spawn_in(window, async move |ticker, cx| {
|
||||
loop {
|
||||
cx.background_executor().timer(STEP).await;
|
||||
let advanced = ticker.update(cx, |ticker: &mut Ticker, cx| {
|
||||
ticker.index = (ticker.index + 1) % VALUES.len();
|
||||
cx.notify();
|
||||
let advanced = cx.update(|_, cx| {
|
||||
ticker.update(cx, |ticker: &mut Ticker, cx| {
|
||||
ticker.index = (ticker.index + 1) % VALUES.len();
|
||||
cx.notify();
|
||||
})
|
||||
});
|
||||
if advanced.is_err() {
|
||||
if advanced.and_then(|inner| inner).is_err() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ pub fn park(window: WindowHandle<Gallery>, cx: &mut AsyncApp) -> Result<()> {
|
||||
}
|
||||
|
||||
fn opened(key: &str, cx: &mut AsyncApp) -> Result<gpui::AnyWindowHandle> {
|
||||
cx.update(|cx| Opened::get(key, cx))?
|
||||
cx.update(|cx| Opened::get(key, cx))
|
||||
.with_context(|| format!("no window opened as {key}"))
|
||||
}
|
||||
|
||||
@@ -231,15 +231,15 @@ pub async fn play(
|
||||
}
|
||||
Step::ExpectClosed(key) => {
|
||||
let handle = opened(key, cx)?;
|
||||
let open = cx.update(|cx| cx.windows().contains(&handle))?;
|
||||
let open = cx.update(|cx| cx.windows().contains(&handle));
|
||||
anyhow::ensure!(!open, "window {key} is still open");
|
||||
cx.update(|cx| Opened::take(key, cx))?;
|
||||
cx.update(|cx| Opened::take(key, cx));
|
||||
log::info!("script: {key} closed");
|
||||
}
|
||||
Step::CloseWindow(key) => {
|
||||
let handle = opened(key, cx)?;
|
||||
handle.update(cx, |_, window, _| window.remove_window())?;
|
||||
cx.update(|cx| Opened::take(key, cx))?;
|
||||
cx.update(|cx| Opened::take(key, cx));
|
||||
}
|
||||
}
|
||||
cx.background_executor().timer(FRAME).await;
|
||||
@@ -439,13 +439,13 @@ async fn target_bounds(
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<Bounds<Pixels>> {
|
||||
let bounds = cx
|
||||
.update(|cx| Probes::get(key, cx))?
|
||||
.update(|cx| Probes::get(key, cx))
|
||||
.with_context(|| format!("probe {key} never rendered"))?;
|
||||
if window.update(cx, |gallery, _, cx| gallery.reveal(bounds, cx))? {
|
||||
cx.background_executor().timer(FRAME).await;
|
||||
}
|
||||
let bounds = cx
|
||||
.update(|cx| Probes::get(key, cx))?
|
||||
.update(|cx| Probes::get(key, cx))
|
||||
.with_context(|| format!("probe {key} vanished after scrolling"))?;
|
||||
log::info!("script: probe {key} at {bounds:?}");
|
||||
Ok(bounds)
|
||||
|
||||
@@ -44,7 +44,7 @@ impl Gallery {
|
||||
}
|
||||
});
|
||||
let focus = cx.focus_handle();
|
||||
window.focus(&focus);
|
||||
window.focus(&focus, cx);
|
||||
Self {
|
||||
page,
|
||||
pass: 0,
|
||||
@@ -71,10 +71,7 @@ impl Gallery {
|
||||
|
||||
/// Viewport height and furthest scroll, in pixels.
|
||||
pub fn scroll_extent(&self) -> (Pixels, Pixels) {
|
||||
(
|
||||
self.scroll.bounds().size.height,
|
||||
self.scroll.max_offset().height,
|
||||
)
|
||||
(self.scroll.bounds().size.height, self.scroll.max_offset().y)
|
||||
}
|
||||
|
||||
/// Scrolls until `target` sits inside the page. True if it moved.
|
||||
@@ -87,7 +84,7 @@ impl Gallery {
|
||||
} else {
|
||||
return false;
|
||||
};
|
||||
let y = (-self.scroll.offset().y + shift).clamp(px(0.0), self.scroll.max_offset().height);
|
||||
let y = (-self.scroll.offset().y + shift).clamp(px(0.0), self.scroll.max_offset().y);
|
||||
self.scroll_to(y, cx);
|
||||
true
|
||||
}
|
||||
|
||||
@@ -62,9 +62,9 @@ pub(super) fn said(host: &Entity<Bench>, cx: &mut VisualTestContext) -> Vec<Stri
|
||||
|
||||
/// Focus on the `stops`th Tab stop from the top.
|
||||
pub(super) fn tab(stops: usize, cx: &mut VisualTestContext) {
|
||||
cx.update(|window, _| {
|
||||
window.blur();
|
||||
(0..stops).for_each(|_| window.focus_next());
|
||||
cx.update(|window, cx| {
|
||||
window.blur(cx);
|
||||
(0..stops).for_each(|_| window.focus_next(cx));
|
||||
});
|
||||
settle(cx);
|
||||
}
|
||||
|
||||
+46
-39
@@ -40,24 +40,29 @@ struct Card {
|
||||
impl Render for Card {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let (view, height) = (cx.entity(), self.height.clone());
|
||||
div()
|
||||
.relative()
|
||||
.w(px(640.0))
|
||||
.child(
|
||||
FileChangeCard::new("card", change("src/theme/lift.rs")).on_decide(
|
||||
move |accepted, _, cx| view.update(cx, |card, _| card.verdicts.push(accepted)),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
canvas(
|
||||
move |bounds, _, _| height.set(bounds.size.height),
|
||||
|_, _, _, _| {},
|
||||
// gpui fills the window with an auto-sized root, so the measured box sits inside one.
|
||||
div().child(
|
||||
div()
|
||||
.relative()
|
||||
.w(px(640.0))
|
||||
.child(
|
||||
FileChangeCard::new("card", change("src/theme/lift.rs")).on_decide(
|
||||
move |accepted, _, cx| {
|
||||
view.update(cx, |card, _| card.verdicts.push(accepted))
|
||||
},
|
||||
),
|
||||
)
|
||||
.absolute()
|
||||
.top_0()
|
||||
.left_0()
|
||||
.size_full(),
|
||||
)
|
||||
.child(
|
||||
canvas(
|
||||
move |bounds, _, _| height.set(bounds.size.height),
|
||||
|_, _, _, _| {},
|
||||
)
|
||||
.absolute()
|
||||
.top_0()
|
||||
.left_0()
|
||||
.size_full(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,8 +119,8 @@ fn review(cx: &mut TestAppContext) -> (Entity<Review>, &mut VisualTestContext) {
|
||||
#[gpui::test]
|
||||
fn accept_all_decides_every_file(cx: &mut TestAppContext) {
|
||||
let (view, cx) = review(cx);
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
press("enter", cx);
|
||||
assert_eq!(
|
||||
view.read_with(cx, |review, _| review.decisions.clone()),
|
||||
@@ -127,7 +132,7 @@ fn accept_all_decides_every_file(cx: &mut TestAppContext) {
|
||||
fn a_cards_verdict_names_its_file(cx: &mut TestAppContext) {
|
||||
let (view, cx) = review(cx);
|
||||
for _ in 0..8 {
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
}
|
||||
press("enter", cx);
|
||||
assert_eq!(
|
||||
@@ -145,26 +150,28 @@ struct Narrow {
|
||||
impl Render for Narrow {
|
||||
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
||||
let height = self.height.clone();
|
||||
div()
|
||||
.relative()
|
||||
.w(self.width)
|
||||
.child(
|
||||
MultiFileDiffReview::new(
|
||||
"narrow",
|
||||
["src/theme/lift.rs", "src/theme/tokens.rs"].map(change),
|
||||
div().child(
|
||||
div()
|
||||
.relative()
|
||||
.w(self.width)
|
||||
.child(
|
||||
MultiFileDiffReview::new(
|
||||
"narrow",
|
||||
["src/theme/lift.rs", "src/theme/tokens.rs"].map(change),
|
||||
)
|
||||
.on_decide(|_, _, _| {}),
|
||||
)
|
||||
.on_decide(|_, _, _| {}),
|
||||
)
|
||||
.child(
|
||||
canvas(
|
||||
move |bounds, _, _| height.set(bounds.size.height),
|
||||
|_, _, _, _| {},
|
||||
)
|
||||
.absolute()
|
||||
.top_0()
|
||||
.left_0()
|
||||
.size_full(),
|
||||
)
|
||||
.child(
|
||||
canvas(
|
||||
move |bounds, _, _| height.set(bounds.size.height),
|
||||
|_, _, _, _| {},
|
||||
)
|
||||
.absolute()
|
||||
.top_0()
|
||||
.left_0()
|
||||
.size_full(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -58,10 +58,10 @@ fn asked(cx: &mut TestAppContext) -> (Entity<Asked>, &mut VisualTestContext) {
|
||||
#[gpui::test]
|
||||
fn a_pick_answers_the_question_and_the_answer_stands_alone(cx: &mut TestAppContext) {
|
||||
let (view, cx) = asked(cx);
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
press("enter", cx);
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
press("enter", cx);
|
||||
assert_eq!(
|
||||
view.read_with(cx, |asked, _| asked.answers.clone()),
|
||||
@@ -74,7 +74,7 @@ fn a_pick_answers_the_question_and_the_answer_stands_alone(cx: &mut TestAppConte
|
||||
fn enter_sends_a_written_answer_and_skips_a_blank_one(cx: &mut TestAppContext) {
|
||||
let (view, cx) = asked(cx);
|
||||
for _ in 0..3 {
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
}
|
||||
cx.simulate_input(" ");
|
||||
press("enter", cx);
|
||||
|
||||
@@ -62,7 +62,7 @@ fn memories(cx: &mut TestAppContext) -> (Entity<Memories>, &mut VisualTestContex
|
||||
#[gpui::test]
|
||||
fn enter_keeps_a_memory_and_empties_the_field(cx: &mut TestAppContext) {
|
||||
let (view, cx) = memories(cx);
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
cx.simulate_input(" ");
|
||||
press("enter", cx);
|
||||
cx.simulate_input("Lifts stay under 20%");
|
||||
@@ -113,9 +113,9 @@ fn a_servers_switch_turns_it_off_and_on(cx: &mut TestAppContext) {
|
||||
let (view, cx) = cx.add_window_view(|_, _| Servers { thrown: Vec::new() });
|
||||
cx.update(|window, _| window.activate_window());
|
||||
settle(cx);
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
press("space", cx);
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
press("space", cx);
|
||||
let thrown = view.read_with(cx, |servers, _| servers.thrown.clone());
|
||||
assert_eq!(thrown, [("files".into(), false), ("web".into(), true)]);
|
||||
@@ -152,9 +152,9 @@ fn every_checkpoint_but_the_current_rewinds(cx: &mut TestAppContext) {
|
||||
let (view, cx) = cx.add_window_view(|_, _| Points { asked: Vec::new() });
|
||||
cx.update(|window, _| window.activate_window());
|
||||
settle(cx);
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
press("enter", cx);
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
press("enter", cx);
|
||||
assert_eq!(
|
||||
view.read_with(cx, |points, _| points.asked.clone()),
|
||||
|
||||
@@ -120,7 +120,7 @@ fn the_prompt_walks_deny_always_then_once(cx: &mut TestAppContext) {
|
||||
cx.update(|window, _| window.activate_window());
|
||||
settle(cx);
|
||||
for _ in 0..3 {
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
}
|
||||
press("enter", cx);
|
||||
assert_eq!(
|
||||
|
||||
+43
-38
@@ -43,8 +43,8 @@ fn a_frame_shows_from_the_keyboard(cx: &mut TestAppContext) {
|
||||
let (view, cx) = cx.add_window_view(|_, _| Browsing { asked: Vec::new() });
|
||||
cx.update(|window, _| window.activate_window());
|
||||
settle(cx);
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
press("enter", cx);
|
||||
assert_eq!(
|
||||
view.read_with(cx, |browsing, _| browsing.asked.clone()),
|
||||
@@ -97,8 +97,8 @@ fn the_source_shows_when_asked(cx: &mut TestAppContext) {
|
||||
cx.update(|window, _| window.activate_window());
|
||||
settle(cx);
|
||||
assert!(!drew.get(), "the preview shows first");
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
press("enter", cx);
|
||||
assert!(drew.get(), "the source shows once asked");
|
||||
}
|
||||
@@ -113,23 +113,26 @@ impl Render for Strip {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let (view, height) = (cx.entity(), self.height.clone());
|
||||
let frames = (0..8).map(|ix| format!("{ix}.png"));
|
||||
div()
|
||||
.relative()
|
||||
.w(px(280.0))
|
||||
.child(
|
||||
BrowserPreview::new("strip", "localhost", frames, 0)
|
||||
.on_show(move |ix, _, cx| view.update(cx, |strip, _| strip.asked.push(ix))),
|
||||
)
|
||||
.child(
|
||||
canvas(
|
||||
move |bounds, _, _| height.set(bounds.size.height),
|
||||
|_, _, _, _| {},
|
||||
// gpui fills the window with an auto-sized root, so the measured box sits inside one.
|
||||
div().child(
|
||||
div()
|
||||
.relative()
|
||||
.w(px(280.0))
|
||||
.child(
|
||||
BrowserPreview::new("strip", "localhost", frames, 0)
|
||||
.on_show(move |ix, _, cx| view.update(cx, |strip, _| strip.asked.push(ix))),
|
||||
)
|
||||
.absolute()
|
||||
.top_0()
|
||||
.left_0()
|
||||
.size_full(),
|
||||
)
|
||||
.child(
|
||||
canvas(
|
||||
move |bounds, _, _| height.set(bounds.size.height),
|
||||
|_, _, _, _| {},
|
||||
)
|
||||
.absolute()
|
||||
.top_0()
|
||||
.left_0()
|
||||
.size_full(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,7 +229,7 @@ fn tab_brings_a_hidden_frame_into_view(cx: &mut TestAppContext) {
|
||||
cx.update(|window, _| window.activate_window());
|
||||
settle(cx);
|
||||
for _ in 0..8 {
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
settle(cx);
|
||||
}
|
||||
settle(cx);
|
||||
@@ -254,24 +257,26 @@ struct Screen {
|
||||
impl Render for Screen {
|
||||
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
||||
let height = self.height.clone();
|
||||
div()
|
||||
.relative()
|
||||
.w(px(480.0))
|
||||
.child(
|
||||
ComputerUseViewer::new("screen", "a.png")
|
||||
.ratio(4.0 / 3.0)
|
||||
.pointer(point(0.5, 0.95)),
|
||||
)
|
||||
.child(
|
||||
canvas(
|
||||
move |bounds, _, _| height.set(bounds.size.height),
|
||||
|_, _, _, _| {},
|
||||
div().child(
|
||||
div()
|
||||
.relative()
|
||||
.w(px(480.0))
|
||||
.child(
|
||||
ComputerUseViewer::new("screen", "a.png")
|
||||
.ratio(4.0 / 3.0)
|
||||
.pointer(point(0.5, 0.95)),
|
||||
)
|
||||
.absolute()
|
||||
.top_0()
|
||||
.left_0()
|
||||
.size_full(),
|
||||
)
|
||||
.child(
|
||||
canvas(
|
||||
move |bounds, _, _| height.set(bounds.size.height),
|
||||
|_, _, _, _| {},
|
||||
)
|
||||
.absolute()
|
||||
.top_0()
|
||||
.left_0()
|
||||
.size_full(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -97,13 +97,15 @@ fn expire(
|
||||
let (weak, generation) = (state.downgrade(), state.read(cx).generation);
|
||||
window.spawn(cx, async move |cx| {
|
||||
cx.background_executor().timer(after).await;
|
||||
let cleared = weak.update(cx, |confirm, cx| {
|
||||
if confirm.generation == generation {
|
||||
clear(confirm);
|
||||
cx.notify();
|
||||
}
|
||||
let cleared = cx.update(|_, cx| {
|
||||
weak.update(cx, |confirm, cx| {
|
||||
if confirm.generation == generation {
|
||||
clear(confirm);
|
||||
cx.notify();
|
||||
}
|
||||
})
|
||||
});
|
||||
if let Err(error) = cleared {
|
||||
if let Err(error) = cleared.and_then(|inner| inner) {
|
||||
log::error!("confirm button: state gone before it expired: {error:#}");
|
||||
}
|
||||
})
|
||||
|
||||
+7
-4
@@ -53,10 +53,13 @@ impl RenderOnce for CopyButton {
|
||||
let weak = state.downgrade();
|
||||
let reset = window.spawn(cx, async move |cx| {
|
||||
cx.background_executor().timer(CONFIRM).await;
|
||||
if let Err(error) = weak.update(cx, |copied, cx| {
|
||||
copied._reset = None;
|
||||
cx.notify();
|
||||
}) {
|
||||
let cleared = cx.update(|_, cx| {
|
||||
weak.update(cx, |copied, cx| {
|
||||
copied._reset = None;
|
||||
cx.notify();
|
||||
})
|
||||
});
|
||||
if let Err(error) = cleared.and_then(|inner| inner) {
|
||||
log::error!("copy button: reset lost its state: {error:#}");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -197,8 +197,8 @@ impl RenderOnce for SegmentedControl {
|
||||
.id(self.id.clone())
|
||||
.relative()
|
||||
.flex()
|
||||
.flex_grow()
|
||||
.flex_shrink()
|
||||
.flex_grow_1()
|
||||
.flex_shrink_1()
|
||||
.min_w(least * count as f32)
|
||||
.items_center()
|
||||
.child(measure_origin(state.clone(), Axis::Horizontal))
|
||||
|
||||
@@ -47,7 +47,7 @@ fn a_loading_button_ignores_presses_and_keeps_its_tab_stop(cx: &mut TestAppConte
|
||||
presses: 0,
|
||||
});
|
||||
let root = view.read_with(cx, |bench, _| bench.root.clone());
|
||||
cx.update(|window, _| window.focus(&root));
|
||||
cx.update(|window, cx| window.focus(&root, cx));
|
||||
press("tab", cx);
|
||||
press("enter", cx);
|
||||
assert_eq!(view.read_with(cx, |bench, _| bench.presses), 0);
|
||||
|
||||
@@ -74,10 +74,10 @@ fn press(key: &str, cx: &mut VisualTestContext) {
|
||||
|
||||
/// Moves focus to the `nth` Tab stop from none.
|
||||
fn tab_to(nth: usize, cx: &mut VisualTestContext) {
|
||||
cx.update(|window, _| {
|
||||
window.blur();
|
||||
cx.update(|window, cx| {
|
||||
window.blur(cx);
|
||||
for _ in 0..nth {
|
||||
window.focus_next();
|
||||
window.focus_next(cx);
|
||||
}
|
||||
});
|
||||
settle(cx);
|
||||
|
||||
@@ -96,9 +96,9 @@ fn a_chip_opens_its_details_and_edit_names_it(cx: &mut TestAppContext) {
|
||||
cx.debug_bounds("event-popover review").is_some(),
|
||||
"the details open"
|
||||
);
|
||||
cx.update(|window, _| {
|
||||
window.focus_next();
|
||||
window.focus_next();
|
||||
cx.update(|window, cx| {
|
||||
window.focus_next(cx);
|
||||
window.focus_next(cx);
|
||||
});
|
||||
press("space", cx);
|
||||
assert_eq!(
|
||||
|
||||
@@ -198,7 +198,7 @@ impl RenderOnce for MindMap {
|
||||
.cursor_pointer()
|
||||
.on_mouse_down(MouseButton::Left, move |event, window, cx| {
|
||||
cx.stop_propagation();
|
||||
window.focus(&hold);
|
||||
window.focus(&hold, cx);
|
||||
if let Some(pick) = &pick {
|
||||
pick(Some(&key), window, cx);
|
||||
}
|
||||
@@ -273,7 +273,7 @@ impl RenderOnce for MindMap {
|
||||
if writing_tab.read(cx).field().is_some() {
|
||||
cx.stop_propagation();
|
||||
writing_tab.update(cx, |editing, cx| editing.finish(false, window, cx));
|
||||
window.focus(&back);
|
||||
window.focus(&back, cx);
|
||||
return;
|
||||
}
|
||||
cx.stop_propagation();
|
||||
|
||||
@@ -137,9 +137,9 @@ fn zoom(_: &Stage, owner: Entity<Stage>) -> AnyElement {
|
||||
fn the_zoom_buttons_step_and_the_percent_resets(cx: &mut TestAppContext) {
|
||||
let (host, cx) = stage(zoom, cx);
|
||||
for nth in [1, 2, 3] {
|
||||
cx.update(|window, _| {
|
||||
window.blur();
|
||||
(0..nth).for_each(|_| window.focus_next());
|
||||
cx.update(|window, cx| {
|
||||
window.blur(cx);
|
||||
(0..nth).for_each(|_| window.focus_next(cx));
|
||||
});
|
||||
cx.simulate_keystrokes("space");
|
||||
cx.simulate_event(gpui::KeyUpEvent {
|
||||
|
||||
@@ -7,9 +7,9 @@ use crate::canvas::{
|
||||
};
|
||||
|
||||
fn tab(stops: usize, cx: &mut VisualTestContext) {
|
||||
cx.update(|window, _| {
|
||||
window.blur();
|
||||
(0..stops).for_each(|_| window.focus_next());
|
||||
cx.update(|window, cx| {
|
||||
window.blur(cx);
|
||||
(0..stops).for_each(|_| window.focus_next(cx));
|
||||
});
|
||||
settle(cx);
|
||||
}
|
||||
|
||||
+1
-1
@@ -343,7 +343,7 @@ impl RenderOnce for ToolLayer {
|
||||
)
|
||||
.on_mouse_down(MouseButton::Left, move |event, window, cx| {
|
||||
cx.stop_propagation();
|
||||
window.focus(&held_focus);
|
||||
window.focus(&held_focus, cx);
|
||||
let bounds = pressed.read(cx).bounds;
|
||||
let at = point_of(event.position, bounds);
|
||||
let local = event.position - bounds.origin;
|
||||
|
||||
@@ -49,7 +49,7 @@ pub(super) fn field(editing: &Entity<Editing>, frame: Frame, cx: &App) -> Option
|
||||
cx.stop_propagation();
|
||||
closing.update(cx, |editing, cx| {
|
||||
editing.finish(true, window, cx);
|
||||
editing.give_back(window);
|
||||
editing.give_back(window, cx);
|
||||
});
|
||||
}
|
||||
})
|
||||
@@ -58,7 +58,7 @@ pub(super) fn field(editing: &Entity<Editing>, frame: Frame, cx: &App) -> Option
|
||||
cx.stop_propagation();
|
||||
keeping.update(cx, |editing, cx| {
|
||||
editing.finish(false, window, cx);
|
||||
editing.give_back(window);
|
||||
editing.give_back(window, cx);
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
+3
-2
@@ -1,8 +1,8 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use gpui::{
|
||||
App, Bounds, Div, ElementId, Entity, InteractiveElement, IntoElement, MouseMoveEvent,
|
||||
ParentElement, PathBuilder, Pixels, Refineable, RenderOnce, SharedString,
|
||||
App, Bounds, Div, ElementId, Entity, HoverListenerMode, InteractiveElement, IntoElement,
|
||||
MouseMoveEvent, ParentElement, PathBuilder, Pixels, Refineable, RenderOnce, SharedString,
|
||||
StatefulInteractiveElement, StyleRefinement, Styled, Window, canvas, div, fill, prelude::*,
|
||||
};
|
||||
|
||||
@@ -368,6 +368,7 @@ impl RenderOnce for SankeyChart {
|
||||
}
|
||||
})
|
||||
})
|
||||
.hover_listener_mode(HoverListenerMode::InputModalityIndependent)
|
||||
.on_hover(move |inside, _, cx| {
|
||||
if !*inside {
|
||||
left.update(cx, |flows, cx| {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use gpui::{
|
||||
App, Div, ElementId, Entity, InteractiveElement, IntoElement, MouseMoveEvent, ParentElement,
|
||||
PathBuilder, Pixels, Refineable, RenderOnce, SharedString, StatefulInteractiveElement,
|
||||
StyleRefinement, Styled, Window, canvas, div, fill,
|
||||
App, Div, ElementId, Entity, HoverListenerMode, InteractiveElement, IntoElement,
|
||||
MouseMoveEvent, ParentElement, PathBuilder, Pixels, Refineable, RenderOnce, SharedString,
|
||||
StatefulInteractiveElement, StyleRefinement, Styled, Window, canvas, div, fill,
|
||||
};
|
||||
|
||||
use super::{
|
||||
@@ -201,6 +201,7 @@ impl RenderOnce for FunnelChart {
|
||||
}
|
||||
})
|
||||
})
|
||||
.hover_listener_mode(HoverListenerMode::InputModalityIndependent)
|
||||
.on_hover(move |inside, _, cx| {
|
||||
if !*inside {
|
||||
left.update(cx, |flows, cx| {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use std::{collections::HashSet, f32::consts::TAU};
|
||||
|
||||
use gpui::{
|
||||
App, Bounds, Div, ElementId, Entity, InteractiveElement, IntoElement, MouseButton,
|
||||
MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, PathBuilder, Pixels, Refineable,
|
||||
RenderOnce, SharedString, StatefulInteractiveElement, StyleRefinement, Styled, Window, canvas,
|
||||
div, prelude::*,
|
||||
App, Bounds, Div, ElementId, Entity, HoverListenerMode, InteractiveElement, IntoElement,
|
||||
MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, PathBuilder, Pixels,
|
||||
Refineable, RenderOnce, SharedString, StatefulInteractiveElement, StyleRefinement, Styled,
|
||||
Window, canvas, div, prelude::*,
|
||||
};
|
||||
|
||||
use super::{
|
||||
@@ -331,6 +331,7 @@ impl RenderOnce for NetworkGraph {
|
||||
}
|
||||
})
|
||||
})
|
||||
.hover_listener_mode(HoverListenerMode::InputModalityIndependent)
|
||||
.on_hover(move |inside, _, cx| {
|
||||
if !*inside {
|
||||
left.update(cx, |graph, cx| {
|
||||
|
||||
+4
-3
@@ -4,9 +4,9 @@ use std::{
|
||||
};
|
||||
|
||||
use gpui::{
|
||||
App, Bounds, Div, ElementId, Entity, Hsla, InteractiveElement, IntoElement, MouseMoveEvent,
|
||||
ParentElement, PathBuilder, Pixels, Point, Refineable, RenderOnce, SharedString,
|
||||
StatefulInteractiveElement, StyleRefinement, Styled, Window, canvas, div, point,
|
||||
App, Bounds, Div, ElementId, Entity, HoverListenerMode, Hsla, InteractiveElement, IntoElement,
|
||||
MouseMoveEvent, ParentElement, PathBuilder, Pixels, Point, Refineable, RenderOnce,
|
||||
SharedString, StatefulInteractiveElement, StyleRefinement, Styled, Window, canvas, div, point,
|
||||
};
|
||||
|
||||
use super::{
|
||||
@@ -326,6 +326,7 @@ impl RenderOnce for PieChart {
|
||||
}
|
||||
})
|
||||
})
|
||||
.hover_listener_mode(HoverListenerMode::InputModalityIndependent)
|
||||
.on_hover(move |inside, _, cx| {
|
||||
if !*inside {
|
||||
left.update(cx, |ring, cx| {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use gpui::{
|
||||
App, Bounds, Div, Entity, InteractiveElement, IntoElement, MouseButton, MouseDownEvent,
|
||||
MouseMoveEvent, MouseUpEvent, PathBuilder, Pixels, Point, Stateful, StatefulInteractiveElement,
|
||||
Styled, Window, canvas, fill, prelude::*, size,
|
||||
App, Bounds, Div, Entity, HoverListenerMode, InteractiveElement, IntoElement, MouseButton,
|
||||
MouseDownEvent, MouseMoveEvent, MouseUpEvent, PathBuilder, Pixels, Point, Stateful,
|
||||
StatefulInteractiveElement, Styled, Window, canvas, fill, prelude::*, size,
|
||||
};
|
||||
|
||||
use super::{
|
||||
@@ -247,6 +247,7 @@ pub(crate) fn answer(
|
||||
}
|
||||
})
|
||||
})
|
||||
.hover_listener_mode(HoverListenerMode::InputModalityIndependent)
|
||||
.on_hover(move |inside, _, cx| {
|
||||
if !*inside {
|
||||
left.update(cx, |state, cx| {
|
||||
|
||||
+5
-3
@@ -5,9 +5,10 @@ use std::{
|
||||
};
|
||||
|
||||
use gpui::{
|
||||
App, Bounds, Div, ElementId, Entity, InteractiveElement, IntoElement, MouseMoveEvent,
|
||||
ParentElement, PathBuilder, Pixels, Point, Refineable, RenderOnce, SharedString,
|
||||
StatefulInteractiveElement, StyleRefinement, Styled, Window, canvas, div, point, prelude::*,
|
||||
App, Bounds, Div, ElementId, Entity, HoverListenerMode, InteractiveElement, IntoElement,
|
||||
MouseMoveEvent, ParentElement, PathBuilder, Pixels, Point, Refineable, RenderOnce,
|
||||
SharedString, StatefulInteractiveElement, StyleRefinement, Styled, Window, canvas, div, point,
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
use super::{
|
||||
@@ -268,6 +269,7 @@ impl RenderOnce for RadarChart {
|
||||
}
|
||||
})
|
||||
})
|
||||
.hover_listener_mode(HoverListenerMode::InputModalityIndependent)
|
||||
.on_hover(move |inside, _, cx| {
|
||||
if !*inside {
|
||||
left.update(cx, |radar, cx| {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use std::f32::consts::{FRAC_PI_2, TAU};
|
||||
|
||||
use gpui::{
|
||||
App, Div, ElementId, Entity, Hsla, InteractiveElement, IntoElement, MouseMoveEvent,
|
||||
ParentElement, Pixels, Refineable, RenderOnce, SharedString, StatefulInteractiveElement,
|
||||
StyleRefinement, Styled, Window, canvas, div,
|
||||
App, Div, ElementId, Entity, HoverListenerMode, Hsla, InteractiveElement, IntoElement,
|
||||
MouseMoveEvent, ParentElement, Pixels, Refineable, RenderOnce, SharedString,
|
||||
StatefulInteractiveElement, StyleRefinement, Styled, Window, canvas, div,
|
||||
};
|
||||
|
||||
use super::{
|
||||
@@ -214,6 +214,7 @@ impl RenderOnce for Sunburst {
|
||||
}
|
||||
})
|
||||
})
|
||||
.hover_listener_mode(HoverListenerMode::InputModalityIndependent)
|
||||
.on_hover(move |inside, _, cx| {
|
||||
if !*inside {
|
||||
left.update(cx, |ring, cx| {
|
||||
|
||||
+3
-3
@@ -197,9 +197,9 @@ fn tab_reaches_a_legend_entry_and_enter_toggles_it(cx: &mut TestAppContext) {
|
||||
let (_, cx) = cx.add_window_view(|_, _| Keyed(seen));
|
||||
cx.update(|window, _| window.activate_window());
|
||||
settle(cx);
|
||||
cx.update(|window, _| {
|
||||
window.focus_next();
|
||||
window.focus_next();
|
||||
cx.update(|window, cx| {
|
||||
window.focus_next(cx);
|
||||
window.focus_next(cx);
|
||||
});
|
||||
settle(cx);
|
||||
cx.simulate_keystrokes("enter");
|
||||
|
||||
+4
-3
@@ -1,9 +1,9 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use gpui::{
|
||||
App, Bounds, Div, ElementId, Entity, Hsla, InteractiveElement, IntoElement, MouseMoveEvent,
|
||||
ParentElement, Pixels, Refineable, RenderOnce, SharedString, StatefulInteractiveElement,
|
||||
StyleRefinement, Styled, Window, canvas, div, fill,
|
||||
App, Bounds, Div, ElementId, Entity, HoverListenerMode, Hsla, InteractiveElement, IntoElement,
|
||||
MouseMoveEvent, ParentElement, Pixels, Refineable, RenderOnce, SharedString,
|
||||
StatefulInteractiveElement, StyleRefinement, Styled, Window, canvas, div, fill,
|
||||
};
|
||||
|
||||
use super::{
|
||||
@@ -53,6 +53,7 @@ pub(crate) fn tracked(
|
||||
}
|
||||
})
|
||||
})
|
||||
.hover_listener_mode(HoverListenerMode::InputModalityIndependent)
|
||||
.on_hover(move |inside, _, cx| {
|
||||
if !*inside {
|
||||
left.update(cx, |tiles, cx| {
|
||||
|
||||
+1
-1
@@ -248,7 +248,7 @@ impl RenderOnce for MessageEditor {
|
||||
let opened = window.use_keyed_state((self.id.clone(), "opened"), cx, |_, _| false);
|
||||
if !*opened.read(cx) {
|
||||
log::info!("message editor: takes focus");
|
||||
window.focus(&self.field.focus_handle(cx));
|
||||
window.focus(&self.field.focus_handle(cx), cx);
|
||||
opened.update(cx, |opened, _| *opened = true);
|
||||
}
|
||||
let text = self.field.read(cx).text().trim().to_string();
|
||||
|
||||
+19
-16
@@ -15,7 +15,7 @@ use crate::{
|
||||
typography::{Ellipsis, format::file_size},
|
||||
};
|
||||
|
||||
/// A box in a picture's own shape: as wide as the picture, but no wider than `widest` or its container.
|
||||
/// A box in a picture's own shape: as wide as the picture, but no wider than `widest` or its container. Hand it to a flex column inside a block: taffy 0.13 sizes the column by its width before its cap.
|
||||
pub(crate) fn shaped(width: f32, height: f32, widest: Pixels) -> Div {
|
||||
assert!(
|
||||
width > 0.0 && height > 0.0,
|
||||
@@ -76,19 +76,21 @@ impl RenderOnce for ImageMessage {
|
||||
.flex_col()
|
||||
.gap_1()
|
||||
.child(
|
||||
shaped(self.size.0, self.size.1, widest)
|
||||
.id(self.id.clone())
|
||||
.relative()
|
||||
.when_some(open, |picture, open| {
|
||||
picture
|
||||
.cursor_pointer()
|
||||
.on_click(move |_, window, cx| open(window, cx))
|
||||
})
|
||||
.child(
|
||||
Image::new((self.id.clone(), "picture"), source(&self.source))
|
||||
.fit(ObjectFit::Contain)
|
||||
.size_full(),
|
||||
),
|
||||
div().child(
|
||||
shaped(self.size.0, self.size.1, widest)
|
||||
.id(self.id.clone())
|
||||
.relative()
|
||||
.when_some(open, |picture, open| {
|
||||
picture
|
||||
.cursor_pointer()
|
||||
.on_click(move |_, window, cx| open(window, cx))
|
||||
})
|
||||
.child(
|
||||
Image::new((self.id.clone(), "picture"), source(&self.source))
|
||||
.fit(ObjectFit::Contain)
|
||||
.size_full(),
|
||||
),
|
||||
),
|
||||
)
|
||||
.children(self.caption.map(|caption| {
|
||||
div()
|
||||
@@ -183,7 +185,7 @@ impl RenderOnce for ImageGrid {
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_0p5();
|
||||
match count {
|
||||
let grid = match count {
|
||||
1 => grid.child(row().child(tile(0))),
|
||||
2 => grid.child(row().child(tile(0)).child(tile(1))),
|
||||
3 => grid.child(
|
||||
@@ -200,7 +202,8 @@ impl RenderOnce for ImageGrid {
|
||||
_ => grid
|
||||
.child(row().child(tile(0)).child(tile(1)))
|
||||
.child(row().child(tile(2)).child(tile(3))),
|
||||
}
|
||||
};
|
||||
div().child(grid)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-2
@@ -249,7 +249,7 @@ impl RenderOnce for VideoMessage {
|
||||
};
|
||||
let playing = self.playing;
|
||||
let toggle = self.on_toggle.clone();
|
||||
shaped(self.size.0, self.size.1, widest)
|
||||
let video = shaped(self.size.0, self.size.1, widest)
|
||||
.id(self.id.clone())
|
||||
.relative()
|
||||
.bg(colors.media_backdrop)
|
||||
@@ -316,6 +316,7 @@ impl RenderOnce for VideoMessage {
|
||||
.bg(colors.on_media.opacity(0.25))
|
||||
.child(div().h_full().w(relative(share)).bg(colors.on_media)),
|
||||
),
|
||||
)
|
||||
);
|
||||
div().child(video)
|
||||
}
|
||||
}
|
||||
@@ -78,7 +78,7 @@ fn citations_and_sources_open_from_the_keyboard(cx: &mut TestAppContext) {
|
||||
});
|
||||
cx.update(|window, _| window.activate_window());
|
||||
settle(cx);
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
release("enter", cx);
|
||||
assert_eq!(
|
||||
cited.read_with(cx, |cited, _| cited.opened),
|
||||
@@ -86,7 +86,7 @@ fn citations_and_sources_open_from_the_keyboard(cx: &mut TestAppContext) {
|
||||
"the badge opens its source"
|
||||
);
|
||||
let folded = frame.borrow().expect("the list is laid out").size.height;
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
release("space", cx);
|
||||
let open = frame.borrow().expect("the list is laid out").size.height;
|
||||
assert!(
|
||||
@@ -126,9 +126,9 @@ fn cards_open_from_the_keyboard(cx: &mut TestAppContext) {
|
||||
let (cards, cx) = cx.add_window_view(|_, _| Cards { opened: Vec::new() });
|
||||
cx.update(|window, _| window.activate_window());
|
||||
settle(cx);
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
release("enter", cx);
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
release("enter", cx);
|
||||
assert_eq!(
|
||||
cards.read_with(cx, |cards, _| cards.opened.clone()),
|
||||
|
||||
@@ -67,7 +67,7 @@ fn open(
|
||||
cx.update(|window, cx| {
|
||||
window.activate_window();
|
||||
let field = compose.read(cx).field.focus_handle(cx);
|
||||
window.focus(&field);
|
||||
window.focus(&field, cx);
|
||||
});
|
||||
settle(cx);
|
||||
(compose, cx)
|
||||
|
||||
@@ -67,9 +67,9 @@ fn a_rows_actions_open_from_the_keyboard(cx: &mut TestAppContext) {
|
||||
let (row, cx) = row(cx);
|
||||
cx.simulate_mouse_move(point(px(10.0), px(200.0)), None, Modifiers::none());
|
||||
settle(cx);
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
settle(cx);
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
settle(cx);
|
||||
press("enter", cx);
|
||||
press("enter", cx);
|
||||
|
||||
+66
-9
@@ -1,10 +1,10 @@
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
use std::{cell::RefCell, rc::Rc, time::Duration};
|
||||
|
||||
use gpui::{
|
||||
AppContext as _, Bounds, Context, Entity, Focusable, InteractiveElement, IntoElement,
|
||||
KeyUpEvent, Keystroke, Modifiers, MouseButton, ParentElement, Pixels, Render, ScrollDelta,
|
||||
ScrollWheelEvent, Styled, TestAppContext, TouchPhase, VisualTestContext, Window, canvas, div,
|
||||
point, px,
|
||||
AnyElement, AppContext as _, Bounds, Context, Entity, Focusable, InteractiveElement,
|
||||
IntoElement, KeyUpEvent, Keystroke, Modifiers, MouseButton, ParentElement, Pixels, Render,
|
||||
ScrollDelta, ScrollWheelEvent, Styled, TestAppContext, TouchPhase, VisualTestContext, Window,
|
||||
canvas, div, point, px,
|
||||
};
|
||||
|
||||
mod cite;
|
||||
@@ -13,8 +13,8 @@ mod history;
|
||||
mod welcome;
|
||||
|
||||
use super::{
|
||||
DateSeparator, ImageMessage, MessageAvatar, MessageBubble, MessageEditor, MessageList, Role,
|
||||
ThinkingBlock,
|
||||
DateSeparator, ImageGrid, ImageMessage, MessageAvatar, MessageBubble, MessageEditor,
|
||||
MessageList, Role, ThinkingBlock, VideoMessage,
|
||||
};
|
||||
use crate::{forms, forms::TextInput, theme::Theme};
|
||||
|
||||
@@ -139,7 +139,7 @@ fn an_edit_saves_only_a_change_and_escape_cancels(cx: &mut TestAppContext) {
|
||||
cx.update(|window, cx| {
|
||||
window.activate_window();
|
||||
let field = edit.read(cx).field.focus_handle(cx);
|
||||
window.focus(&field);
|
||||
window.focus(&field, cx);
|
||||
});
|
||||
settle(cx);
|
||||
cx.simulate_keystrokes("cmd-enter");
|
||||
@@ -306,6 +306,63 @@ fn a_picture_fits_a_narrow_column_in_its_own_shape(cx: &mut TestAppContext) {
|
||||
assert_eq!(bounds.size.height, px(160.0), "240 wide at 3:2 is 160 tall");
|
||||
}
|
||||
|
||||
/// A message in a narrow flex column, the column measured.
|
||||
struct Column {
|
||||
frame: Measured,
|
||||
message: fn() -> AnyElement,
|
||||
}
|
||||
|
||||
impl Render for Column {
|
||||
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
||||
let frame = self.frame.clone();
|
||||
div().size_full().child(
|
||||
div()
|
||||
.w(px(240.0))
|
||||
.relative()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.child((self.message)())
|
||||
.child(
|
||||
canvas(
|
||||
move |bounds, _, _| *frame.borrow_mut() = Some(bounds),
|
||||
|_, _, _, _| {},
|
||||
)
|
||||
.absolute()
|
||||
.top_0()
|
||||
.left_0()
|
||||
.size_full(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn column_height(message: fn() -> AnyElement, cx: &mut TestAppContext) -> Pixels {
|
||||
let frame = Measured::default();
|
||||
let seen = frame.clone();
|
||||
let (_, cx) = cx.add_window_view(move |_, _| Column {
|
||||
frame: seen,
|
||||
message,
|
||||
});
|
||||
settle(cx);
|
||||
frame.borrow().expect("the column is laid out").size.height
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn a_video_and_a_grid_fit_a_narrow_flex_column_in_their_shape(cx: &mut TestAppContext) {
|
||||
cx.update(Theme::init);
|
||||
let video = || {
|
||||
VideoMessage::new("video", "dunes.jpg", 960.0, 640.0, Duration::from_secs(84))
|
||||
.into_any_element()
|
||||
};
|
||||
assert_eq!(column_height(video, cx), px(160.0), "240 wide at 3:2");
|
||||
let grid = || ImageGrid::new("grid", ["a.jpg", "b.jpg"]).into_any_element();
|
||||
assert_eq!(
|
||||
column_height(grid, cx),
|
||||
px(120.0),
|
||||
"two side by side at 2:1"
|
||||
);
|
||||
}
|
||||
|
||||
/// Reasoning folded under its time, the block's box measured.
|
||||
struct Reasoning {
|
||||
frame: Measured,
|
||||
@@ -350,7 +407,7 @@ fn the_reasoning_opens_from_the_keyboard(cx: &mut TestAppContext) {
|
||||
cx.update(|window, _| window.activate_window());
|
||||
settle(cx);
|
||||
let closed = frame.borrow().expect("the block is laid out").size.height;
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
cx.simulate_keystrokes("space");
|
||||
cx.simulate_event(KeyUpEvent {
|
||||
keystroke: Keystroke::parse("space").expect("space parses"),
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::ops::Range;
|
||||
|
||||
use gpui::{
|
||||
App, Bounds, ContentMask, Entity, FontWeight, IntoElement, Pixels, RenderOnce, SharedString,
|
||||
Styled, TextRun, Window, canvas, fill, point, size,
|
||||
Styled, TextAlign, TextRun, Window, canvas, fill, point, size,
|
||||
};
|
||||
|
||||
use super::peers::Peer;
|
||||
@@ -92,9 +92,14 @@ impl RenderOnce for RemoteCursor {
|
||||
bottom_right: radius,
|
||||
bottom_left: gpui::Pixels::ZERO,
|
||||
}));
|
||||
if let Err(error) =
|
||||
line.paint(point(flag.left() + pad, flag.top()), height, window, cx)
|
||||
{
|
||||
if let Err(error) = line.paint(
|
||||
point(flag.left() + pad, flag.top()),
|
||||
height,
|
||||
TextAlign::Left,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
) {
|
||||
log::error!("remote cursor: name did not paint: {error:#}");
|
||||
}
|
||||
},
|
||||
|
||||
+2
-2
@@ -49,7 +49,7 @@ fn open(following: bool, cx: &mut TestAppContext) -> (Entity<Desk>, &mut VisualT
|
||||
cx.update(|window, cx| {
|
||||
window.activate_window();
|
||||
let field = desk.read(cx).field.clone();
|
||||
window.focus(&field.focus_handle(cx));
|
||||
window.focus(&field.focus_handle(cx), cx);
|
||||
});
|
||||
cx.run_until_parked();
|
||||
(desk, cx)
|
||||
@@ -84,7 +84,7 @@ fn following_begun_from_outside_still_stops_on_escape(cx: &mut TestAppContext) {
|
||||
let (desk, cx) = open(false, cx);
|
||||
cx.update(|window, cx| {
|
||||
let outside = desk.read(cx).outside.focus_handle(cx);
|
||||
window.focus(&outside);
|
||||
window.focus(&outside, cx);
|
||||
});
|
||||
desk.update(cx, |desk, cx| {
|
||||
desk.following = true;
|
||||
|
||||
@@ -95,7 +95,7 @@ fn press(key: &str, cx: &mut VisualTestContext) {
|
||||
#[gpui::test]
|
||||
fn arrows_step_a_focused_tile_past_its_neighbours_and_shift_grows_it(cx: &mut TestAppContext) {
|
||||
let (host, cx) = wall(cx);
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
settle(cx);
|
||||
press("down", cx);
|
||||
assert_eq!(place(&host, "a", cx), (0, 4, 6, 2), "a went below c");
|
||||
@@ -115,7 +115,7 @@ fn arrows_step_a_focused_tile_past_its_neighbours_and_shift_grows_it(cx: &mut Te
|
||||
fn arrows_in_a_control_inside_a_card_leave_the_tiles_where_they_are(cx: &mut TestAppContext) {
|
||||
let (host, cx) = wall(cx);
|
||||
let inside = host.read_with(cx, |wall, _| wall.inside.clone());
|
||||
cx.update(|window, _| window.focus(&inside));
|
||||
cx.update(|window, cx| window.focus(&inside, cx));
|
||||
settle(cx);
|
||||
press("right", cx);
|
||||
press("down", cx);
|
||||
@@ -168,9 +168,9 @@ fn said(host: &Entity<Board>, cx: &mut VisualTestContext) -> Vec<String> {
|
||||
}
|
||||
|
||||
fn tab(stops: usize, cx: &mut VisualTestContext) {
|
||||
cx.update(|window, _| {
|
||||
window.blur();
|
||||
(0..stops).for_each(|_| window.focus_next());
|
||||
cx.update(|window, cx| {
|
||||
window.blur(cx);
|
||||
(0..stops).for_each(|_| window.focus_next(cx));
|
||||
});
|
||||
settle(cx);
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ use std::{
|
||||
};
|
||||
|
||||
use gpui::{
|
||||
AnyElement, App, Div, ElementId, InteractiveElement, IntoElement, MouseButton, ParentElement,
|
||||
RenderOnce, StatefulInteractiveElement, StyleRefinement, Styled, Task, Window, div, prelude::*,
|
||||
relative,
|
||||
AnyElement, App, Div, ElementId, HoverListenerMode, InteractiveElement, IntoElement,
|
||||
MouseButton, ParentElement, RenderOnce, StatefulInteractiveElement, StyleRefinement, Styled,
|
||||
Task, Window, div, prelude::*, relative,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@@ -268,6 +268,7 @@ impl RenderOnce for Carousel {
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_2()
|
||||
.hover_listener_mode(HoverListenerMode::InputModalityIndependent)
|
||||
.on_hover(move |hovered, _, cx| {
|
||||
hover.update(cx, |deck, cx| {
|
||||
deck.hovered = *hovered;
|
||||
|
||||
@@ -161,6 +161,9 @@ fn wait(time: Duration, cx: &mut VisualTestContext) {
|
||||
#[gpui::test]
|
||||
fn autoplay_turns_the_carousel_and_holds_while_pointed_at(cx: &mut TestAppContext) {
|
||||
let (places, cx) = deck(cx);
|
||||
// The pointer starts at the corner, over the deck, which gpui now counts as a hover.
|
||||
cx.simulate_mouse_move(point(px(600.0), px(400.0)), None, Modifiers::none());
|
||||
settle(cx);
|
||||
assert_eq!(front(&places), Some(0));
|
||||
wait(Duration::from_secs(5), cx);
|
||||
assert_eq!(front(&places), Some(1), "five seconds turn it once");
|
||||
@@ -174,6 +177,17 @@ fn autoplay_turns_the_carousel_and_holds_while_pointed_at(cx: &mut TestAppContex
|
||||
assert_eq!(front(&places), Some(2));
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn a_key_under_a_resting_pointer_keeps_the_carousel_held(cx: &mut TestAppContext) {
|
||||
let (places, cx) = deck(cx);
|
||||
cx.simulate_mouse_move(point(px(100.0), px(50.0)), None, Modifiers::none());
|
||||
settle(cx);
|
||||
cx.simulate_keystrokes("a");
|
||||
settle(cx);
|
||||
wait(Duration::from_secs(12), cx);
|
||||
assert_eq!(front(&places), Some(0), "still held");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn left_from_the_first_slide_wraps_to_the_last(cx: &mut TestAppContext) {
|
||||
let (places, cx) = deck(cx);
|
||||
|
||||
+4
-2
@@ -2,8 +2,8 @@ use std::rc::Rc;
|
||||
|
||||
use gpui::{
|
||||
App, Bounds, ElementId, Hsla, InteractiveElement, IntoElement, MouseButton, MouseDownEvent,
|
||||
MouseMoveEvent, ParentElement, Pixels, RenderOnce, SharedString, Styled, Window, canvas, div,
|
||||
fill, point, size,
|
||||
MouseMoveEvent, ParentElement, Pixels, RenderOnce, SharedString, Styled, TextAlign, Window,
|
||||
canvas, div, fill, point, size,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@@ -297,6 +297,8 @@ impl RenderOnce for Flamegraph {
|
||||
(row - pixels * 1.2) / 2.0,
|
||||
),
|
||||
pixels * 1.2,
|
||||
TextAlign::Left,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
|
||||
+3
-2
@@ -1,8 +1,8 @@
|
||||
use std::{ops::Range, rc::Rc};
|
||||
|
||||
use gpui::{
|
||||
App, ElementId, InteractiveElement, IntoElement, MouseButton, ParentElement, RenderOnce,
|
||||
StatefulInteractiveElement, Styled, Window, div, prelude::*, uniform_list,
|
||||
App, ElementId, HoverListenerMode, InteractiveElement, IntoElement, MouseButton, ParentElement,
|
||||
RenderOnce, StatefulInteractiveElement, Styled, Window, div, prelude::*, uniform_list,
|
||||
};
|
||||
|
||||
use super::OnIndex;
|
||||
@@ -126,6 +126,7 @@ impl RenderOnce for HexViewer {
|
||||
.id((id.clone(), format!("{key}-{at}")))
|
||||
.px_0p5()
|
||||
.when_some(wash(at), |cell, wash| cell.bg(wash))
|
||||
.hover_listener_mode(HoverListenerMode::InputModalityIndependent)
|
||||
.on_hover(move |on, _, cx| {
|
||||
hover.update(cx, |hovered, cx| {
|
||||
let next = if *on { Some(at) } else { None };
|
||||
|
||||
+2
-2
@@ -124,8 +124,8 @@ fn tab_reaches_each_span_and_enter_picks_it(cx: &mut TestAppContext) {
|
||||
let (_, cx) = cx.add_window_view(|_, _| Spans(store));
|
||||
cx.update(|window, _| window.activate_window());
|
||||
settle(cx);
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
cx.simulate_keystrokes("enter");
|
||||
settle(cx);
|
||||
cx.simulate_event(KeyUpEvent {
|
||||
|
||||
@@ -65,9 +65,9 @@ fn said(host: &Entity<Bench>, cx: &mut VisualTestContext) -> Vec<String> {
|
||||
}
|
||||
|
||||
fn tab(stops: usize, cx: &mut VisualTestContext) {
|
||||
cx.update(|window, _| {
|
||||
window.blur();
|
||||
(0..stops).for_each(|_| window.focus_next());
|
||||
cx.update(|window, cx| {
|
||||
window.blur(cx);
|
||||
(0..stops).for_each(|_| window.focus_next(cx));
|
||||
});
|
||||
settle(cx);
|
||||
}
|
||||
@@ -163,9 +163,9 @@ fn form(bench: &Bench, _: &mut Window, _: &mut App, owner: Entity<Bench>) -> Any
|
||||
|
||||
/// Save is the form's last stop, so a step back from nothing reaches it.
|
||||
fn last(cx: &mut VisualTestContext) {
|
||||
cx.update(|window, _| {
|
||||
window.blur();
|
||||
window.focus_prev();
|
||||
cx.update(|window, cx| {
|
||||
window.blur(cx);
|
||||
window.focus_prev(cx);
|
||||
});
|
||||
settle(cx);
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ impl BlockEditor {
|
||||
&& let Some(field) = block.fields.get(field)
|
||||
{
|
||||
field.update(cx, |field, cx| field.select(selection, cx));
|
||||
window.focus(&field.focus_handle(cx));
|
||||
window.focus(&field.focus_handle(cx), cx);
|
||||
}
|
||||
self.current = snapshot;
|
||||
cx.emit(BlockEvent::Changed);
|
||||
|
||||
@@ -349,7 +349,7 @@ impl BlockEditor {
|
||||
if let Some(at) = at {
|
||||
field.update(cx, |field, cx| field.select(at..at, cx));
|
||||
}
|
||||
window.focus(&field.focus_handle(cx));
|
||||
window.focus(&field.focus_handle(cx), cx);
|
||||
}
|
||||
|
||||
/// Adds a block after `after`, or first, and puts the caret in it.
|
||||
|
||||
@@ -248,7 +248,7 @@ pub(super) fn table(
|
||||
let wide = span(&merged, columns, row, column);
|
||||
line = line.child(
|
||||
div()
|
||||
.flex_grow()
|
||||
.flex_grow_1()
|
||||
.flex_basis(gpui::relative(wide as f32 / columns as f32))
|
||||
.min_w_0()
|
||||
.px_2()
|
||||
|
||||
@@ -59,7 +59,7 @@ fn caret(editor: &Entity<BlockEditor>, ix: usize, at: usize, cx: &mut VisualTest
|
||||
cx.update(|window, cx| {
|
||||
let field = editor.read(cx).blocks[ix].fields[0].clone();
|
||||
field.update(cx, |field, cx| field.select(at..at, cx));
|
||||
window.focus(&field.focus_handle(cx));
|
||||
window.focus(&field.focus_handle(cx), cx);
|
||||
});
|
||||
settle(cx);
|
||||
}
|
||||
@@ -211,7 +211,7 @@ fn select(
|
||||
cx.update(|window, cx| {
|
||||
let field = editor.read(cx).blocks[ix].fields[field].clone();
|
||||
field.update(cx, |field, cx| field.select(range, cx));
|
||||
window.focus(&field.focus_handle(cx));
|
||||
window.focus(&field.focus_handle(cx), cx);
|
||||
});
|
||||
settle(cx);
|
||||
}
|
||||
@@ -280,7 +280,7 @@ fn keys_into_a_merged_cell_land_on_the_cell_that_shows(cx: &mut TestAppContext)
|
||||
select(&editor, 0, 0, 1..1, cx);
|
||||
cx.update(|window, cx| {
|
||||
let field = editor.read(cx).blocks[0].fields[1].clone();
|
||||
window.focus(&field.focus_handle(cx));
|
||||
window.focus(&field.focus_handle(cx), cx);
|
||||
});
|
||||
settle(cx);
|
||||
cx.simulate_keystrokes("down");
|
||||
|
||||
@@ -105,7 +105,7 @@ impl RenderOnce for LinkEditor {
|
||||
log::info!("link editor: open over {:?}", linking.range);
|
||||
let written = linking.address.clone().unwrap_or_default();
|
||||
address.update(cx, |field, cx| field.set_text(written, cx));
|
||||
window.focus(&address.focus_handle(cx));
|
||||
window.focus(&address.focus_handle(cx), cx);
|
||||
editing.update(cx, |editing, _| editing.seeded = true);
|
||||
}
|
||||
let focus = takeover.read(cx).focus.clone();
|
||||
|
||||
@@ -135,7 +135,7 @@ fn open(
|
||||
input.select(0..0, cx);
|
||||
}
|
||||
});
|
||||
window.focus(&input.focus_handle(cx));
|
||||
window.focus(&input.focus_handle(cx), cx);
|
||||
let blur = {
|
||||
let (state, field) = (state.clone(), field.clone());
|
||||
cx.subscribe(&input, move |_, event: &InputEvent, cx| {
|
||||
|
||||
@@ -214,7 +214,7 @@ impl RenderOnce for ReadingProgress {
|
||||
move |bounds, _, window, _| {
|
||||
let share = read_share(
|
||||
f32::from(scroll.offset().y),
|
||||
f32::from(scroll.max_offset().height),
|
||||
f32::from(scroll.max_offset().y),
|
||||
);
|
||||
let filled = Bounds::new(
|
||||
bounds.origin,
|
||||
|
||||
@@ -239,7 +239,7 @@ fn escape_leaves_zen_turned_on_from_outside_and_hands_focus_back(cx: &mut TestAp
|
||||
cx.update(|window, cx| {
|
||||
window.activate_window();
|
||||
let outside = zen.read(cx).outside.focus_handle(cx);
|
||||
window.focus(&outside);
|
||||
window.focus(&outside, cx);
|
||||
});
|
||||
settle(cx);
|
||||
zen.update(cx, |zen, cx| {
|
||||
|
||||
+7
-1
@@ -247,7 +247,13 @@ pub(crate) fn listen<E: InteractiveElement>(root: E, cx: &mut Context<CodeEditor
|
||||
e.delete_by(|buffer, at| buffer.word_start(at)..at, cx)
|
||||
});
|
||||
let root = on!(root, DeleteLineLeft, |e, _, cx| e.delete_by(line_left, cx));
|
||||
let root = on!(root, Newline, |e, _, cx| e.newline(cx));
|
||||
let root = on!(root, Newline, |e, window, cx| {
|
||||
if e.focus.is_focused(window) {
|
||||
e.newline(cx)
|
||||
} else {
|
||||
cx.propagate()
|
||||
}
|
||||
});
|
||||
let root = on!(root, Indent, |e, _, cx| e.indent(cx));
|
||||
let root = on!(root, Outdent, |e, _, cx| e.outdent(cx));
|
||||
let root = on!(root, Undo, |e, _, cx| e.undo(cx));
|
||||
|
||||
+1
-1
@@ -352,7 +352,7 @@ impl CodeEditor {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
window.focus(&self.focus);
|
||||
window.focus(&self.focus, cx);
|
||||
let at = self.offset_in(line, event.position.x, cx);
|
||||
let chosen = match event.click_count {
|
||||
2 => {
|
||||
|
||||
+2
-2
@@ -397,9 +397,9 @@ impl CodeEditor {
|
||||
self.marks.chat = line;
|
||||
if line.is_some() {
|
||||
let focus = self.chat_field.read(cx).focus().clone();
|
||||
window.focus(&focus);
|
||||
window.focus(&focus, cx);
|
||||
} else {
|
||||
window.focus(&self.focus);
|
||||
window.focus(&self.focus, cx);
|
||||
cx.emit(EditorEvent::ChatClosed);
|
||||
}
|
||||
cx.notify();
|
||||
|
||||
+24
-1
@@ -1,6 +1,6 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use gpui::{Entity, EntityInputHandler, TestAppContext, VisualTestContext};
|
||||
use gpui::{Entity, EntityInputHandler, KeyUpEvent, Keystroke, TestAppContext, VisualTestContext};
|
||||
|
||||
use super::{CodeEditor, GhostText, InlayHint, LineNumbers, layout::Row};
|
||||
use crate::theme::Theme;
|
||||
@@ -140,6 +140,29 @@ fn a_read_only_editor_ignores_typing_and_undo(cx: &mut TestAppContext) {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn enter_presses_the_banner_button_inside_the_editor(cx: &mut TestAppContext) {
|
||||
let (editor, cx) = editor("x", cx);
|
||||
cx.update(|window, cx| {
|
||||
super::keys::bind_keys(cx);
|
||||
editor.update(cx, |editor, cx| editor.set_read_only(true, cx));
|
||||
window.refresh();
|
||||
});
|
||||
cx.run_until_parked();
|
||||
let focus = editor.read_with(cx, |editor, _| editor.focus.clone());
|
||||
cx.update(|window, cx| {
|
||||
window.focus(&focus, cx);
|
||||
window.focus_next(cx);
|
||||
});
|
||||
cx.run_until_parked();
|
||||
cx.simulate_keystrokes("enter");
|
||||
cx.simulate_event(KeyUpEvent {
|
||||
keystroke: Keystroke::parse("enter").expect("a key"),
|
||||
});
|
||||
cx.run_until_parked();
|
||||
assert!(!editor.read_with(cx, |editor, _| editor.is_read_only()));
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn a_hint_past_a_multi_line_ghost_stays_in_its_row(cx: &mut TestAppContext) {
|
||||
let (editor, cx) = editor("call(x)\nnext", cx);
|
||||
|
||||
+1
-1
@@ -268,7 +268,7 @@ impl Render for CodeEditor {
|
||||
range.map(|ix| editor.row(ix, window, cx)).collect()
|
||||
}),
|
||||
)
|
||||
.track_scroll(self.scroll.clone())
|
||||
.track_scroll(&self.scroll)
|
||||
.size_full();
|
||||
let (top, focus, input) = (cx.entity(), self.focus.clone(), cx.entity());
|
||||
let unlock = cx.entity();
|
||||
|
||||
@@ -79,7 +79,7 @@ fn an_async_view_shows_its_value_once_loaded(cx: &mut TestAppContext) {
|
||||
fn a_failed_load_tries_again_from_its_button(cx: &mut TestAppContext) {
|
||||
let (loads, shows, cx) = report(true, cx);
|
||||
assert_eq!((loads.load(Ordering::SeqCst), shows.get()), (1, 0));
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
press("enter", cx);
|
||||
settle(cx);
|
||||
assert_eq!(loads.load(Ordering::SeqCst), 2);
|
||||
|
||||
@@ -75,7 +75,7 @@ fn desk(cx: &mut TestAppContext) -> (Entity<Desk>, &mut VisualTestContext) {
|
||||
});
|
||||
settle(cx);
|
||||
let root = view.read_with(cx, |desk, _| desk.root.clone());
|
||||
cx.update(|window, _| window.focus(&root));
|
||||
cx.update(|window, cx| window.focus(&root, cx));
|
||||
(view, cx)
|
||||
}
|
||||
|
||||
@@ -161,6 +161,19 @@ fn the_pointer_on_the_stack_holds_the_clock(cx: &mut TestAppContext) {
|
||||
assert!(toasts(&view, cx).is_empty());
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn typing_under_a_resting_pointer_keeps_the_hold(cx: &mut TestAppContext) {
|
||||
let (view, cx) = desk(cx);
|
||||
let id = push(&view, Toast::new("Saved"), cx);
|
||||
let screen = cx.update(|window, _| window.viewport_size());
|
||||
let on_toast = point(screen.width - px(120.0), screen.height - px(48.0));
|
||||
cx.simulate_mouse_move(on_toast, None, Modifiers::none());
|
||||
settle(cx);
|
||||
press("a", cx);
|
||||
wait(Duration::from_secs(20), cx);
|
||||
assert_eq!(toasts(&view, cx), [(id, false)], "a key leaves the pointer");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn a_fifth_toast_sends_the_oldest_away(cx: &mut TestAppContext) {
|
||||
let (view, cx) = desk(cx);
|
||||
|
||||
@@ -73,7 +73,7 @@ fn shelf(
|
||||
});
|
||||
settle(cx);
|
||||
let root = view.read_with(cx, |shelf, _| shelf.root.clone());
|
||||
cx.update(|window, _| window.focus(&root));
|
||||
cx.update(|window, cx| window.focus(&root, cx));
|
||||
(view, cx)
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ fn retry_shows_only_once_saving_failed(cx: &mut TestAppContext) {
|
||||
});
|
||||
settle(cx);
|
||||
let root = view.read_with(cx, |shelf, _| shelf.root.clone());
|
||||
cx.update(|window, _| window.focus(&root));
|
||||
cx.update(|window, cx| window.focus(&root, cx));
|
||||
for _ in 0..3 {
|
||||
press("tab", cx);
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@ use std::{
|
||||
};
|
||||
|
||||
use gpui::{
|
||||
Animation, AnimationExt, AnyElement, App, ClickEvent, Context, Corner, ElementId, Entity,
|
||||
FontWeight, InteractiveElement, IntoElement, ParentElement, Pixels, RenderOnce, SharedString,
|
||||
StatefulInteractiveElement, Styled, Task, Window, anchored, canvas, div, point, prelude::*,
|
||||
relative,
|
||||
Anchor, Animation, AnimationExt, AnyElement, App, ClickEvent, Context, ElementId, Entity,
|
||||
FontWeight, HoverListenerMode, InteractiveElement, IntoElement, ParentElement, Pixels,
|
||||
RenderOnce, SharedString, StatefulInteractiveElement, Styled, Task, Window, anchored, canvas,
|
||||
div, point, prelude::*, relative,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@@ -230,6 +230,7 @@ impl RenderOnce for ToastViewport {
|
||||
.flex()
|
||||
.flex_col()
|
||||
.w(width)
|
||||
.hover_listener_mode(HoverListenerMode::InputModalityIndependent)
|
||||
.on_hover(move |hovered, _, cx| {
|
||||
hold.update(cx, |toaster, cx| toaster.hold(*hovered, cx))
|
||||
})
|
||||
@@ -238,7 +239,7 @@ impl RenderOnce for ToastViewport {
|
||||
(self.id, "raised"),
|
||||
anchored()
|
||||
.position(point(viewport.width - margin, viewport.height - margin))
|
||||
.anchor(Corner::BottomRight)
|
||||
.anchor(Anchor::BottomRight)
|
||||
.child(stack),
|
||||
)
|
||||
.with_priority(1)
|
||||
|
||||
@@ -70,10 +70,10 @@ fn exploring<'a>(
|
||||
|
||||
/// Moves focus to the `nth` Tab stop from none: the three views, the path's first level, then the view's own.
|
||||
fn tab_to(nth: usize, cx: &mut VisualTestContext) {
|
||||
cx.update(|window, _| {
|
||||
window.blur();
|
||||
cx.update(|window, cx| {
|
||||
window.blur(cx);
|
||||
for _ in 0..nth {
|
||||
window.focus_next();
|
||||
window.focus_next(cx);
|
||||
}
|
||||
});
|
||||
settle(cx);
|
||||
|
||||
@@ -17,10 +17,10 @@ use crate::{
|
||||
|
||||
/// Moves focus to the `nth` Tab stop from none.
|
||||
fn tab_to(nth: usize, cx: &mut VisualTestContext) {
|
||||
cx.update(|window, _| {
|
||||
window.blur();
|
||||
cx.update(|window, cx| {
|
||||
window.blur(cx);
|
||||
for _ in 0..nth {
|
||||
window.focus_next();
|
||||
window.focus_next(cx);
|
||||
}
|
||||
});
|
||||
settle(cx);
|
||||
|
||||
@@ -103,7 +103,7 @@ fn heard(
|
||||
#[gpui::test]
|
||||
fn the_arrows_move_by_tile_and_by_row_and_enter_opens(cx: &mut TestAppContext) {
|
||||
let (view, cx) = tiled(None, cx);
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
for key in ["right", "right", "down", "down", "cmd-left"] {
|
||||
press(key, cx);
|
||||
}
|
||||
@@ -116,7 +116,7 @@ fn the_arrows_move_by_tile_and_by_row_and_enter_opens(cx: &mut TestAppContext) {
|
||||
#[gpui::test]
|
||||
fn the_keys_start_from_the_tile_picked(cx: &mut TestAppContext) {
|
||||
let (view, cx) = tiled(Some("c"), cx);
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
press("down", cx);
|
||||
assert_eq!(heard(&view, cx).0, ["f"]);
|
||||
}
|
||||
|
||||
@@ -11,10 +11,10 @@ use crate::files::{
|
||||
|
||||
/// Moves focus to the `nth` Tab stop from none.
|
||||
fn tab_to(nth: usize, cx: &mut VisualTestContext) {
|
||||
cx.update(|window, _| {
|
||||
window.blur();
|
||||
cx.update(|window, cx| {
|
||||
window.blur(cx);
|
||||
for _ in 0..nth {
|
||||
window.focus_next();
|
||||
window.focus_next(cx);
|
||||
}
|
||||
});
|
||||
settle(cx);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use gpui::{
|
||||
App, Bounds, Div, Entity, InteractiveElement, MouseButton, MouseDownEvent, MouseMoveEvent,
|
||||
MouseUpEvent, Pixels, Point, ScrollWheelEvent, Stateful, StatefulInteractiveElement, Styled,
|
||||
Subscription, Window,
|
||||
App, Bounds, Div, Entity, HoverListenerMode, InteractiveElement, MouseButton, MouseDownEvent,
|
||||
MouseMoveEvent, MouseUpEvent, Pixels, Point, ScrollWheelEvent, Stateful,
|
||||
StatefulInteractiveElement, Styled, Subscription, Window,
|
||||
};
|
||||
|
||||
use super::{
|
||||
@@ -167,6 +167,7 @@ impl Steering {
|
||||
cx.stop_propagation();
|
||||
hold(&wheeled, &sync_wheel, cx, Some(next), None);
|
||||
})
|
||||
.hover_listener_mode(HoverListenerMode::InputModalityIndependent)
|
||||
.on_hover(move |inside, _, cx| {
|
||||
if !*inside {
|
||||
left.update(cx, |stage, _| {
|
||||
|
||||
@@ -329,7 +329,7 @@ impl RenderOnce for Calendar {
|
||||
.grid_cols(7)
|
||||
.gap_0p5()
|
||||
.on_hover(move |over, window, cx| {
|
||||
if !*over {
|
||||
if !*over && !window.last_input_was_keyboard() {
|
||||
leave.update(cx, |hover, cx| {
|
||||
*hover = None;
|
||||
cx.notify();
|
||||
|
||||
@@ -60,7 +60,7 @@ pub(crate) fn toggle(
|
||||
state.preview = None;
|
||||
cx.notify();
|
||||
});
|
||||
window.focus(if open { &inner } else { trigger });
|
||||
window.focus(if open { &inner } else { trigger }, cx);
|
||||
}
|
||||
|
||||
/// What a picker field shows while closed, and how it sits.
|
||||
|
||||
+3
-1
@@ -110,7 +110,9 @@ impl RenderOnce for InputGroup {
|
||||
.bg(colors.surface)
|
||||
.text_size(theme.text_size(text_size(self.size)))
|
||||
.text_color(colors.fg)
|
||||
.on_mouse_down(MouseButton::Left, move |_, window, _| window.focus(&focus))
|
||||
.on_mouse_down(MouseButton::Left, move |_, window, cx| {
|
||||
window.focus(&focus, cx)
|
||||
})
|
||||
.children(self.before.into_iter().map(|block| addon(block, true)))
|
||||
.child(div().flex_1().min_w_0().px(pad).child(self.state))
|
||||
.children(self.after.into_iter().map(|block| addon(block, false)))
|
||||
|
||||
+2
-2
@@ -73,7 +73,7 @@ impl RenderOnce for HotkeyInput {
|
||||
let bare = !stroke.modifiers.modified();
|
||||
let next = match stroke.key.as_str() {
|
||||
"escape" if bare => {
|
||||
window.blur();
|
||||
window.blur(cx);
|
||||
return;
|
||||
}
|
||||
"backspace" | "delete" if bare => None,
|
||||
@@ -87,7 +87,7 @@ impl RenderOnce for HotkeyInput {
|
||||
next.as_ref()
|
||||
.map_or("cleared".into(), |stroke| stroke.unparse())
|
||||
);
|
||||
window.blur();
|
||||
window.blur(cx);
|
||||
if let Some(on_change) = &on_change {
|
||||
on_change(next, window, cx);
|
||||
}
|
||||
|
||||
+5
-5
@@ -33,7 +33,7 @@ impl Editing {
|
||||
});
|
||||
let all = field.read(cx).text().len();
|
||||
field.update(cx, |input, cx| input.select(0..all, cx));
|
||||
window.focus(&field.read(cx).focus().clone());
|
||||
window.focus(&field.read(cx).focus().clone(), cx);
|
||||
let owner = state.clone();
|
||||
let events = window.subscribe(&field, cx, move |_, event, window, cx| match event {
|
||||
InputEvent::Submit => owner.update(cx, |editing, cx| {
|
||||
@@ -58,10 +58,10 @@ impl Editing {
|
||||
self.back = Some(back);
|
||||
}
|
||||
|
||||
pub(crate) fn give_back(&mut self, window: &mut Window) {
|
||||
pub(crate) fn give_back(&mut self, window: &mut Window, cx: &mut App) {
|
||||
if let Some(back) = self.back.take() {
|
||||
log::info!("inline edit: focus handed back");
|
||||
window.focus(&back);
|
||||
window.focus(&back, cx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ impl RenderOnce for InlineEdit {
|
||||
cx.stop_propagation();
|
||||
escape.update(cx, |editing, cx| {
|
||||
editing.finish(true, window, cx);
|
||||
editing.give_back(window);
|
||||
editing.give_back(window, cx);
|
||||
});
|
||||
}
|
||||
})
|
||||
@@ -143,7 +143,7 @@ impl RenderOnce for InlineEdit {
|
||||
cx.stop_propagation();
|
||||
enter.update(cx, |editing, cx| {
|
||||
editing.finish(false, window, cx);
|
||||
editing.give_back(window);
|
||||
editing.give_back(window, cx);
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
+4
-2
@@ -115,7 +115,9 @@ impl RenderOnce for Input {
|
||||
} else {
|
||||
colors.fg
|
||||
})
|
||||
.on_mouse_down(MouseButton::Left, move |_, window, _| window.focus(&focus))
|
||||
.on_mouse_down(MouseButton::Left, move |_, window, cx| {
|
||||
window.focus(&focus, cx)
|
||||
})
|
||||
.children(self.prefix)
|
||||
.child(div().flex_1().min_w_0().child(self.state))
|
||||
.when(self.clearable && !empty && !disabled, |frame| {
|
||||
@@ -125,7 +127,7 @@ impl RenderOnce for Input {
|
||||
.tooltip("Clear")
|
||||
.on_click(move |_, window, cx| {
|
||||
state.update(cx, |input, cx| input.set_text("", cx));
|
||||
window.focus(&state.read(cx).focus().clone());
|
||||
window.focus(&state.read(cx).focus().clone(), cx);
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use gpui::{
|
||||
Animation, AnimationExt, AnyElement, App, Bounds, Corner, Div, ElementId, Entity,
|
||||
Anchor, Animation, AnimationExt, AnyElement, App, Bounds, Div, ElementId, Entity,
|
||||
InteractiveElement, IntoElement, MouseButton, ParentElement, Pixels, ScrollHandle,
|
||||
SharedString, Stateful, StatefulInteractiveElement, Styled, Window, anchored, canvas, div,
|
||||
prelude::*,
|
||||
@@ -216,7 +216,7 @@ pub(crate) fn float_height(
|
||||
let placed = if up {
|
||||
anchored()
|
||||
.position(anchor.origin)
|
||||
.anchor(Corner::BottomLeft)
|
||||
.anchor(Anchor::BottomLeft)
|
||||
.child(
|
||||
div()
|
||||
.child(content)
|
||||
|
||||
+1
-1
@@ -184,7 +184,7 @@ fn render_rows(id: &ElementId, state: Entity<Rows>, add: &'static str, cx: &App)
|
||||
grow.update(cx, |rows, cx| {
|
||||
rows.push(&[], window, cx);
|
||||
let last = rows.rows.last().expect("just pushed");
|
||||
window.focus(&last[0].0.read(cx).focus().clone());
|
||||
window.focus(&last[0].0.read(cx).focus().clone(), cx);
|
||||
rows.report(window, cx);
|
||||
cx.notify();
|
||||
})
|
||||
|
||||
+2
-2
@@ -150,7 +150,7 @@ pub(crate) fn grid(
|
||||
.capture_action(move |_: &Down, window, cx| {
|
||||
if count > 0 {
|
||||
cx.stop_propagation();
|
||||
window.focus(&into);
|
||||
window.focus(&into, cx);
|
||||
}
|
||||
})
|
||||
.capture_action(move |_: &Enter, window, cx| {
|
||||
@@ -231,7 +231,7 @@ pub(crate) fn grid(
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.track_scroll(scroll)
|
||||
.track_scroll(&scroll)
|
||||
.h(theme.list_max_height());
|
||||
div()
|
||||
.track_focus(&focus)
|
||||
|
||||
+1
-1
@@ -256,7 +256,7 @@ impl RenderOnce for RadioGroup {
|
||||
};
|
||||
cx.stop_propagation();
|
||||
let next = step(&choices, at, by);
|
||||
window.focus(&handles[next]);
|
||||
window.focus(&handles[next], cx);
|
||||
pick(&choices[next].value, window, cx);
|
||||
})
|
||||
.children(radios)
|
||||
|
||||
+1
-1
@@ -238,7 +238,7 @@ impl Track {
|
||||
_ => 0,
|
||||
};
|
||||
grab.update(cx, |grab, _| *grab = index);
|
||||
window.focus(&focus[index]);
|
||||
window.focus(&focus[index], cx);
|
||||
jump(index, next, window, cx);
|
||||
})
|
||||
});
|
||||
|
||||
+3
-1
@@ -229,7 +229,9 @@ impl RenderOnce for TagInput {
|
||||
.bg(colors.surface)
|
||||
.text_size(theme.text_size(TextSize::Base))
|
||||
.text_color(colors.fg)
|
||||
.on_mouse_down(MouseButton::Left, move |_, window, _| window.focus(&focus))
|
||||
.on_mouse_down(MouseButton::Left, move |_, window, cx| {
|
||||
window.focus(&focus, cx)
|
||||
})
|
||||
.capture_action(move |_: &Up, _, cx| {
|
||||
if open {
|
||||
cx.stop_propagation();
|
||||
|
||||
+10
-10
@@ -53,7 +53,7 @@ impl Render for Radios {
|
||||
fn radio_arrows_skip_disabled_choices_and_wrap(cx: &mut TestAppContext) {
|
||||
setup(cx);
|
||||
let (view, cx) = cx.add_window_view(|_, _| Radios { chosen: "a".into() });
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
cx.simulate_keystrokes("down");
|
||||
assert_eq!(view.read_with(cx, |view, _| view.chosen.clone()), "c");
|
||||
cx.simulate_keystrokes("down");
|
||||
@@ -84,7 +84,7 @@ impl Render for Picker {
|
||||
fn select_opens_moves_and_picks_from_the_keyboard(cx: &mut TestAppContext) {
|
||||
setup(cx);
|
||||
let (view, cx) = cx.add_window_view(|_, _| Picker { chosen: "a".into() });
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
cx.simulate_keystrokes("down down enter");
|
||||
assert_eq!(view.read_with(cx, |view, _| view.chosen.clone()), "b");
|
||||
}
|
||||
@@ -213,7 +213,7 @@ impl Render for Level {
|
||||
fn slider_keys_step_jump_and_reach_the_end(cx: &mut TestAppContext) {
|
||||
setup(cx);
|
||||
let (view, cx) = cx.add_window_view(|_, _| Level { value: 10.0 });
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
cx.simulate_keystrokes("right pageup");
|
||||
assert_eq!(view.read_with(cx, |view, _| view.value), 21.0);
|
||||
cx.simulate_keystrokes("end");
|
||||
@@ -242,7 +242,7 @@ impl Render for Agree {
|
||||
fn space_toggles_a_focused_checkbox(cx: &mut TestAppContext) {
|
||||
setup(cx);
|
||||
let (view, cx) = cx.add_window_view(|_, _| Agree { on: false });
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
cx.simulate_keystrokes("space");
|
||||
cx.simulate_event(KeyUpEvent {
|
||||
keystroke: Keystroke::parse("space").unwrap(),
|
||||
@@ -254,7 +254,7 @@ fn space_toggles_a_focused_checkbox(cx: &mut TestAppContext) {
|
||||
fn a_disabled_choice_still_leaves_the_group_reachable(cx: &mut TestAppContext) {
|
||||
setup(cx);
|
||||
let (view, cx) = cx.add_window_view(|_, _| Radios { chosen: "b".into() });
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
cx.simulate_keystrokes("down");
|
||||
assert_eq!(view.read_with(cx, |view, _| view.chosen.clone()), "c");
|
||||
}
|
||||
@@ -281,7 +281,7 @@ impl Render for Several {
|
||||
fn a_full_enter_press_opens_a_multi_select_and_keeps_it_open(cx: &mut TestAppContext) {
|
||||
setup(cx);
|
||||
let (view, cx) = cx.add_window_view(|_, _| Several { chosen: Vec::new() });
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
cx.simulate_keystrokes("enter");
|
||||
release("enter", cx);
|
||||
cx.simulate_keystrokes("enter");
|
||||
@@ -333,7 +333,7 @@ fn typed<'a>(
|
||||
#[gpui::test]
|
||||
fn enter_in_a_combobox_skips_a_disabled_first_row(cx: &mut TestAppContext) {
|
||||
let (view, cx) = typed(None, cx);
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
cx.simulate_keystrokes("enter");
|
||||
assert_eq!(
|
||||
view.read_with(cx, |view, _| view.chosen.clone()),
|
||||
@@ -386,7 +386,7 @@ fn a_disabled_rating_ignores_the_arrows(cx: &mut TestAppContext) {
|
||||
value: 2,
|
||||
disabled: false,
|
||||
});
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
view.update(cx, |view, cx| {
|
||||
view.disabled = true;
|
||||
cx.notify();
|
||||
@@ -440,7 +440,7 @@ fn a_shut_select_may_be_empty(cx: &mut TestAppContext) {
|
||||
setup(cx);
|
||||
let (_, cx) = cx.add_window_view(|_, _| Empty(true));
|
||||
cx.run_until_parked();
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
assert!(cx.update(|window, cx| window.focused(cx).is_none()));
|
||||
}
|
||||
|
||||
@@ -469,7 +469,7 @@ fn a_long_choice_keeps_its_list_inside_the_window(cx: &mut TestAppContext) {
|
||||
let (_, cx) = cx.add_window_view(|_, _| Named);
|
||||
cx.simulate_resize(gpui::size(px(280.0), px(600.0)));
|
||||
cx.run_until_parked();
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
cx.simulate_keystrokes("enter");
|
||||
release("enter", cx);
|
||||
cx.run_until_parked();
|
||||
|
||||
+54
-14
@@ -1,6 +1,8 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use gpui::{
|
||||
Context, IntoElement, KeyUpEvent, Keystroke, ParentElement, Render, Styled, TestAppContext,
|
||||
Window, div,
|
||||
Context, IntoElement, KeyUpEvent, Keystroke, Modifiers, ParentElement, Render, Styled,
|
||||
TestAppContext, Window, div, point, px,
|
||||
};
|
||||
use jiff::civil::{Date, Time, date, time};
|
||||
|
||||
@@ -38,7 +40,7 @@ fn a_date_picker_opens_and_picks_from_the_keyboard(cx: &mut TestAppContext) {
|
||||
value: Some(date(2026, 9, 10)),
|
||||
disabled: false,
|
||||
});
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
cx.simulate_keystrokes("enter right enter");
|
||||
assert_eq!(
|
||||
view.read_with(cx, |view, _| view.value),
|
||||
@@ -70,7 +72,7 @@ impl Render for Span {
|
||||
fn a_range_picker_takes_two_picks_in_either_order(cx: &mut TestAppContext) {
|
||||
setup(cx);
|
||||
let (view, cx) = cx.add_window_view(|_, _| Span { value: None });
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
cx.simulate_keystrokes("enter enter left left enter");
|
||||
assert_eq!(
|
||||
view.read_with(cx, |view, _| view.value),
|
||||
@@ -106,7 +108,7 @@ fn a_time_picker_steps_with_the_arrows(cx: &mut TestAppContext) {
|
||||
let (view, cx) = cx.add_window_view(|_, _| Clock {
|
||||
value: Some(time(9, 30, 0, 0)),
|
||||
});
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
cx.simulate_keystrokes("enter down down");
|
||||
assert_eq!(
|
||||
view.read_with(cx, |view, _| view.value),
|
||||
@@ -143,7 +145,7 @@ fn a_calendar_refuses_days_past_its_max(cx: &mut TestAppContext) {
|
||||
picked: None,
|
||||
max: Some(TODAY),
|
||||
});
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
cx.simulate_keystrokes("right enter");
|
||||
assert_eq!(view.read_with(cx, |view, _| view.picked), None);
|
||||
cx.simulate_keystrokes("left enter");
|
||||
@@ -157,7 +159,7 @@ fn a_picker_disabled_while_open_stops_picking(cx: &mut TestAppContext) {
|
||||
value: Some(date(2026, 9, 10)),
|
||||
disabled: false,
|
||||
});
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
cx.simulate_keystrokes("enter");
|
||||
view.update(cx, |view, cx| {
|
||||
view.disabled = true;
|
||||
@@ -177,18 +179,18 @@ fn the_month_buttons_carry_the_cursor(cx: &mut TestAppContext) {
|
||||
picked: None,
|
||||
max: None,
|
||||
});
|
||||
cx.update(|window, _| {
|
||||
window.focus_next();
|
||||
window.focus_next();
|
||||
window.focus_next();
|
||||
cx.update(|window, cx| {
|
||||
window.focus_next(cx);
|
||||
window.focus_next(cx);
|
||||
window.focus_next(cx);
|
||||
});
|
||||
cx.simulate_keystrokes("enter");
|
||||
cx.simulate_event(KeyUpEvent {
|
||||
keystroke: Keystroke::parse("enter").unwrap(),
|
||||
});
|
||||
cx.update(|window, _| {
|
||||
window.focus_prev();
|
||||
window.focus_prev();
|
||||
cx.update(|window, cx| {
|
||||
window.focus_prev(cx);
|
||||
window.focus_prev(cx);
|
||||
});
|
||||
cx.simulate_keystrokes("enter");
|
||||
assert_eq!(
|
||||
@@ -196,3 +198,41 @@ fn the_month_buttons_carry_the_cursor(cx: &mut TestAppContext) {
|
||||
Some(date(2026, 10, 25))
|
||||
);
|
||||
}
|
||||
|
||||
/// A calendar that records every preview it reports.
|
||||
struct Watched {
|
||||
seen: Vec<Option<Date>>,
|
||||
}
|
||||
|
||||
impl Render for Watched {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let view = cx.entity();
|
||||
Calendar::new("watched")
|
||||
.selected(TODAY)
|
||||
.today(TODAY)
|
||||
.on_hover(Rc::new(move |date, _, cx| {
|
||||
view.update(cx, |view, cx| {
|
||||
view.seen.push(date);
|
||||
cx.notify();
|
||||
})
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn keys_keep_the_preview_under_a_resting_pointer(cx: &mut TestAppContext) {
|
||||
setup(cx);
|
||||
let (view, cx) = cx.add_window_view(|_, _| Watched { seen: Vec::new() });
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
cx.simulate_mouse_move(point(px(120.0), px(200.0)), None, Modifiers::none());
|
||||
let pointed = view.read_with(cx, |view, _| view.seen.last().copied().flatten());
|
||||
assert!(pointed.is_some(), "the pointer rests on a day");
|
||||
cx.simulate_keystrokes("pagedown");
|
||||
cx.update(|window, _| window.refresh());
|
||||
cx.run_until_parked();
|
||||
assert_eq!(
|
||||
view.read_with(cx, |view, _| view.seen.last().copied().flatten()),
|
||||
Some(date(2026, 10, 25)),
|
||||
"the keyboard's day stays the preview"
|
||||
);
|
||||
}
|
||||
@@ -69,7 +69,7 @@ fn text(field: &Entity<TextInput>, cx: &mut VisualTestContext) -> String {
|
||||
}
|
||||
|
||||
fn focus(field: &Entity<TextInput>, cx: &mut VisualTestContext) {
|
||||
cx.update(|window, cx| window.focus(&field.read(cx).focus().clone()));
|
||||
cx.update(|window, cx| window.focus(&field.read(cx).focus().clone(), cx));
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
@@ -80,7 +80,7 @@ fn tab_reaches_fields_and_skips_disabled_ones(cx: &mut TestAppContext) {
|
||||
let mut order = Vec::new();
|
||||
for _ in 0..2 {
|
||||
order.push(cx.update(|window, cx| {
|
||||
window.focus_next();
|
||||
window.focus_next(cx);
|
||||
fields
|
||||
.iter()
|
||||
.position(|field| field.read(cx).focus().is_focused(window))
|
||||
@@ -96,9 +96,9 @@ fn enter_on_a_clear_button_clears_its_own_field(cx: &mut TestAppContext) {
|
||||
field.update(cx, |input, cx| input.set_text("kept", cx));
|
||||
}
|
||||
cx.run_until_parked();
|
||||
cx.update(|window, _| {
|
||||
window.focus_next();
|
||||
window.focus_next();
|
||||
cx.update(|window, cx| {
|
||||
window.focus_next(cx);
|
||||
window.focus_next(cx);
|
||||
});
|
||||
cx.run_until_parked();
|
||||
cx.simulate_keystrokes("enter");
|
||||
@@ -177,7 +177,7 @@ impl Render for Stepped {
|
||||
fn stepping_starts_from_the_typed_number(cx: &mut TestAppContext) {
|
||||
setup(cx);
|
||||
let (view, cx) = cx.add_window_view(|_, _| Stepped { value: 4.0 });
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
cx.simulate_keystrokes("cmd-a");
|
||||
cx.simulate_input("20");
|
||||
cx.simulate_keystrokes("up");
|
||||
@@ -188,7 +188,7 @@ fn stepping_starts_from_the_typed_number(cx: &mut TestAppContext) {
|
||||
fn a_typed_number_reaches_the_owner_before_any_blur(cx: &mut TestAppContext) {
|
||||
setup(cx);
|
||||
let (view, cx) = cx.add_window_view(|_, _| Stepped { value: 100.0 });
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
cx.simulate_keystrokes("cmd-a");
|
||||
cx.simulate_input("250");
|
||||
assert_eq!(
|
||||
@@ -224,7 +224,7 @@ fn select_all_then_typing_replaces_the_pin(cx: &mut TestAppContext) {
|
||||
let (view, cx) = cx.add_window_view(|_, _| Pin {
|
||||
code: String::new(),
|
||||
});
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
cx.simulate_input("4821");
|
||||
cx.simulate_keystrokes("cmd-a");
|
||||
cx.simulate_input("1234");
|
||||
|
||||
@@ -29,7 +29,7 @@ impl Render for Icons {
|
||||
fn an_icon_is_searched_walked_and_picked_from_the_keyboard(cx: &mut TestAppContext) {
|
||||
setup(cx);
|
||||
let (view, cx) = cx.add_window_view(|_, _| Icons { picked: None });
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
cx.simulate_input("arrow down");
|
||||
cx.simulate_keystrokes("down right enter");
|
||||
let wanted: Vec<IconName> = IconName::ALL
|
||||
@@ -135,7 +135,7 @@ fn an_inline_edit_takes_tab_opens_on_enter_and_takes_focus_back(cx: &mut TestApp
|
||||
cx.update(|cx| cx.bind_keys([KeyBinding::new("tab", FocusNext, None)]));
|
||||
let (view, cx) = cx.add_window_view(|_, _| Titled(Vec::new()));
|
||||
cx.update(|window, _| window.activate_window());
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
release("enter", cx);
|
||||
cx.simulate_input("Stairs");
|
||||
release("enter", cx);
|
||||
|
||||
@@ -59,7 +59,7 @@ fn signup(cx: &mut TestAppContext) -> (Entity<Signup>, &mut VisualTestContext) {
|
||||
#[gpui::test]
|
||||
fn cmd_enter_in_a_field_submits_the_form_once(cx: &mut TestAppContext) {
|
||||
let (view, cx) = signup(cx);
|
||||
cx.update(|window, _| window.focus_next());
|
||||
cx.update(|window, cx| window.focus_next(cx));
|
||||
cx.simulate_input("Ada");
|
||||
cx.simulate_keystrokes("cmd-enter");
|
||||
let counts = view.read_with(cx, |view, _| (view.sent, view.field_submits));
|
||||
@@ -69,9 +69,9 @@ fn cmd_enter_in_a_field_submits_the_form_once(cx: &mut TestAppContext) {
|
||||
#[gpui::test]
|
||||
fn cmd_enter_on_any_control_in_a_form_submits_it(cx: &mut TestAppContext) {
|
||||
let (view, cx) = signup(cx);
|
||||
cx.update(|window, _| {
|
||||
window.focus_next();
|
||||
window.focus_next();
|
||||
cx.update(|window, cx| {
|
||||
window.focus_next(cx);
|
||||
window.focus_next(cx);
|
||||
});
|
||||
cx.simulate_keystrokes("cmd-enter");
|
||||
assert_eq!(
|
||||
@@ -120,16 +120,16 @@ fn field_array_buttons_remove_their_own_row_and_add_one(cx: &mut TestAppContext)
|
||||
let (view, cx) = cx.add_window_view(|_, _| Rows {
|
||||
rows: vec!["a", "b", "c"],
|
||||
});
|
||||
cx.update(|window, _| {
|
||||
window.focus_next();
|
||||
window.focus_next();
|
||||
cx.update(|window, cx| {
|
||||
window.focus_next(cx);
|
||||
window.focus_next(cx);
|
||||
});
|
||||
press("space", cx);
|
||||
assert_eq!(view.read_with(cx, |view, _| view.rows.clone()), ["a", "c"]);
|
||||
cx.update(|window, _| {
|
||||
window.blur();
|
||||
cx.update(|window, cx| {
|
||||
window.blur(cx);
|
||||
for _ in 0..3 {
|
||||
window.focus_next();
|
||||
window.focus_next(cx);
|
||||
}
|
||||
});
|
||||
press("space", cx);
|
||||
|
||||
Loaded 100 of 199 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user