Every page and scripted state was shot with gpui 0.2.2 and with the port, light and dark, full width and at 280px, judged pair by pair by agents with skeptics, and every page's log diffed. What differed is fixed, each with a test that fails without it where the test platform can show it: - The gallery's harness takes posted mouse moves (gpui_macos listens to a tracking area), posts each drag move twice (it no longer replays a held drag), and waits three frames before each shot. - Rolling digits: a block column, so taffy 0.13 lends the cell no baseline, and a line of whole device pixels, since gpui snaps each length on its own. DescriptionList's row aligns ends. - Margins that taffy 0.13 collapses through a plain block: the drawer's pull and snap, the stagger and transition rise, the dialog and palette rises, the fullscreen dialog's slide. - An Input keeps room to type in a squeezed row; the field rows drop w_full, whose reason was taffy 0.9's. AGENTS.md's rules that named gpui 0.2.2 or taffy 0.9 were re-tested against both sources and rewritten, and new ones say what the port changed. TASKS.md's tags follow: what the new gpui makes possible is prove. Captions, doc comments and README say what holds now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
233 lines
7.3 KiB
Rust
233 lines
7.3 KiB
Rust
use ely_gpui_component::{
|
|
forms::{Cascade, Cascader, Choice, Combobox, ListBox, MultiSelect, Select, TransferList},
|
|
primitives::IconName,
|
|
};
|
|
use gpui::{App, IntoElement, ParentElement, SharedString, Styled, Window, div, px};
|
|
|
|
use super::text::field;
|
|
use crate::{
|
|
probe::probe,
|
|
ui::{blocked, keep, section, set, specimen, specimens},
|
|
};
|
|
|
|
fn cities() -> Vec<Choice> {
|
|
[
|
|
("ams", "Amsterdam"),
|
|
("ber", "Berlin"),
|
|
("cph", "Copenhagen"),
|
|
("lis", "Lisbon"),
|
|
("lon", "London"),
|
|
("osl", "Oslo"),
|
|
("par", "Paris"),
|
|
("vie", "Vienna"),
|
|
]
|
|
.into_iter()
|
|
.map(|(value, label)| Choice::new(value, label))
|
|
.collect()
|
|
}
|
|
|
|
pub fn select(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
|
let view = keep("select-view", || SharedString::from("list"), window, cx);
|
|
let now = view.read(cx).clone();
|
|
section(
|
|
"Select / NativeSelect",
|
|
"A button that opens its list. Arrows move, Enter picks, Escape closes.",
|
|
cx,
|
|
)
|
|
.child(
|
|
specimens()
|
|
.child(specimen(
|
|
"view",
|
|
probe(
|
|
"select",
|
|
div().w(px(240.0)).child(
|
|
Select::new(
|
|
"select-view",
|
|
[
|
|
Choice::new("list", "List").icon(IconName::List),
|
|
Choice::new("board", "Board").icon(IconName::Kanban),
|
|
Choice::new("calendar", "Calendar").icon(IconName::Calendar),
|
|
Choice::new("gallery", "Gallery")
|
|
.icon(IconName::LayoutGrid)
|
|
.disabled(),
|
|
],
|
|
)
|
|
.selected(now)
|
|
.on_change(move |value, _, cx| set(&view, value.clone(), cx)),
|
|
),
|
|
),
|
|
cx,
|
|
))
|
|
.child(specimen(
|
|
"disabled",
|
|
div().w(px(200.0)).child(
|
|
Select::new("select-off", [Choice::new("utc", "UTC")])
|
|
.selected("utc")
|
|
.disabled(true),
|
|
),
|
|
cx,
|
|
)),
|
|
)
|
|
.child(blocked(
|
|
"NativeSelect: gpui has no native popup menu, so Select draws its own.",
|
|
cx,
|
|
))
|
|
}
|
|
|
|
pub fn list_box(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
|
let picked = keep(
|
|
"listbox-cities",
|
|
|| vec![SharedString::from("lis")],
|
|
window,
|
|
cx,
|
|
);
|
|
let now = picked.read(cx).clone();
|
|
section(
|
|
"ListBox",
|
|
"A list chosen from in place. Focus it and use the arrows, Space or Enter.",
|
|
cx,
|
|
)
|
|
.child(
|
|
div().w(px(240.0)).child(
|
|
ListBox::new("listbox-cities", cities())
|
|
.selected(now)
|
|
.on_change(move |next, _, cx| set(&picked, next.to_vec(), cx)),
|
|
),
|
|
)
|
|
}
|
|
|
|
pub fn combobox(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
|
let city = field("input-city", window, cx, |input| {
|
|
input.placeholder("Search cities")
|
|
});
|
|
let tag = field("input-tag", window, cx, |input| {
|
|
input.placeholder("Any label")
|
|
});
|
|
let chosen = keep("combobox-city", || None::<SharedString>, window, cx);
|
|
let now = chosen.read(cx).clone();
|
|
let labels: Vec<Choice> = ["design", "docs", "infra", "motion", "release"]
|
|
.into_iter()
|
|
.map(|label| Choice::new(label, label))
|
|
.collect();
|
|
let mut combo = Combobox::new("combobox-city", &city, cities())
|
|
.on_change(move |value, _, cx| set(&chosen, Some(value.clone()), cx));
|
|
if let Some(value) = now {
|
|
combo = combo.selected(value);
|
|
}
|
|
section(
|
|
"Combobox / Autocomplete",
|
|
"Type to filter. A combobox keeps a listed value; an autocomplete keeps any text.",
|
|
cx,
|
|
)
|
|
.child(
|
|
specimens()
|
|
.child(specimen(
|
|
"combobox",
|
|
probe("combobox", div().w(px(260.0)).child(combo)),
|
|
cx,
|
|
))
|
|
.child(specimen(
|
|
"autocomplete",
|
|
div()
|
|
.w(px(260.0))
|
|
.child(Combobox::new("combobox-tag", &tag, labels).free()),
|
|
cx,
|
|
)),
|
|
)
|
|
}
|
|
|
|
pub fn multi_select(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
|
let picked = keep(
|
|
"multi-cities",
|
|
|| vec![SharedString::from("ber"), SharedString::from("osl")],
|
|
window,
|
|
cx,
|
|
);
|
|
let now = picked.read(cx).clone();
|
|
section(
|
|
"MultiSelect",
|
|
"Several values as chips. The list stays open while you pick; Backspace drops the last.",
|
|
cx,
|
|
)
|
|
.child(probe(
|
|
"multi",
|
|
div().w(px(360.0)).child(
|
|
MultiSelect::new("multi-cities", cities())
|
|
.selected(now)
|
|
.placeholder("Pick cities")
|
|
.on_change(move |next, _, cx| set(&picked, next.to_vec(), cx)),
|
|
),
|
|
))
|
|
}
|
|
|
|
pub fn cascader(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
|
let path = keep(
|
|
"cascade-office",
|
|
|| {
|
|
vec![
|
|
SharedString::from("eu"),
|
|
SharedString::from("pt"),
|
|
SharedString::from("lis"),
|
|
]
|
|
},
|
|
window,
|
|
cx,
|
|
);
|
|
let now = path.read(cx).clone();
|
|
let world = [
|
|
Cascade::new("am", "Americas")
|
|
.child(
|
|
Cascade::new("us", "United States")
|
|
.child(Cascade::new("nyc", "New York"))
|
|
.child(Cascade::new("sfo", "San Francisco")),
|
|
)
|
|
.child(Cascade::new("br", "Brazil").child(Cascade::new("sao", "São Paulo"))),
|
|
Cascade::new("eu", "Europe")
|
|
.child(
|
|
Cascade::new("pt", "Portugal")
|
|
.child(Cascade::new("lis", "Lisbon"))
|
|
.child(Cascade::new("opo", "Porto")),
|
|
)
|
|
.child(Cascade::new("de", "Germany").child(Cascade::new("ber", "Berlin"))),
|
|
Cascade::new("as", "Asia")
|
|
.child(Cascade::new("jp", "Japan").child(Cascade::new("tyo", "Tokyo"))),
|
|
];
|
|
section(
|
|
"CascadeSelect / Cascader",
|
|
"Each column opens the next. Right and Left walk the levels; a leaf picks the path.",
|
|
cx,
|
|
)
|
|
.child(probe(
|
|
"cascader",
|
|
div().w(px(320.0)).child(
|
|
Cascader::new("cascade-office", world)
|
|
.selected(now)
|
|
.on_change(move |next, _, cx| set(&path, next.to_vec(), cx)),
|
|
),
|
|
))
|
|
}
|
|
|
|
pub fn transfer(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
|
let chosen = keep(
|
|
"transfer-cities",
|
|
|| vec![SharedString::from("par")],
|
|
window,
|
|
cx,
|
|
);
|
|
let now = chosen.read(cx).clone();
|
|
section(
|
|
"TransferList",
|
|
"Mark rows on one side, then move them across.",
|
|
cx,
|
|
)
|
|
.child(probe(
|
|
"transfer",
|
|
div().w(px(560.0)).child(
|
|
TransferList::new("transfer-cities", cities())
|
|
.chosen(now)
|
|
.titles("Cities", "On the tour")
|
|
.on_change(move |next, _, cx| set(&chosen, next.to_vec(), cx)),
|
|
),
|
|
))
|
|
}
|