KanbanBoard lays columns of cards side by side. A card drags to a place in any column: it rides raised at the pointer, a slot opens where it would land, the other cards glide aside on motion::Flip, and the drop asks the owner through on_move(card, column, index); a drop in its own place asks nothing. Option with an arrow moves the focused card, whose focus the board keys by card so it follows it across columns. A press, Enter or Space opens it. Sprint Board is this board. IssueCard shows an issue's status, name, assignee, title, priority, labels and due day. TaskDetailPanel lays a task out in full: its title edits in place, its status, priority, assignee and due day change where they stand, and its notes and steps follow; each edit hands the whole task on, and a blank title is not taken. LabelManager tends a team's labels: a hue picked from a popover, a name edited in place, a delete, and a field that makes a new one; names stay unique in any case, and a refused name says why. The fill rule changes: min_w_full, taken up in T30d, resolves against a block's outer width in taffy 0.9, so a field in a padded card ran past its padding. A component that fills now takes no width on its root, which blocks and flex columns stretch; FormField and the new roots do. The chart's hue names move to theme::HUE_NAMES, which the event editor and the label manager share.
59 lines
2.0 KiB
Rust
59 lines
2.0 KiB
Rust
use gpui::Pixels;
|
|
|
|
/// Where a card whose middle sits at `middle` lands among rows of `heights` from a column's `top`: past every row whose middle is above it.
|
|
pub(crate) fn landing(heights: &[Pixels], top: Pixels, middle: Pixels) -> usize {
|
|
let mut y = top;
|
|
let mut index = 0;
|
|
for height in heights {
|
|
if y + *height / 2.0 < middle {
|
|
index += 1;
|
|
}
|
|
y += *height;
|
|
}
|
|
index
|
|
}
|
|
|
|
/// Where Option and an arrow take the card at `index` of `column`, among columns of `lens` cards: up and down within it, left and right to the same place or the end.
|
|
pub(crate) fn stepped(
|
|
lens: &[usize],
|
|
column: usize,
|
|
index: usize,
|
|
key: &str,
|
|
) -> Option<(usize, usize)> {
|
|
match key {
|
|
"up" => (index > 0).then(|| (column, index - 1)),
|
|
"down" => (index + 1 < lens[column]).then(|| (column, index + 1)),
|
|
"left" => (column > 0).then(|| (column - 1, index.min(lens[column - 1]))),
|
|
"right" => (column + 1 < lens.len()).then(|| (column + 1, index.min(lens[column + 1]))),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use gpui::px;
|
|
|
|
use super::{landing, stepped};
|
|
|
|
#[test]
|
|
fn a_card_lands_past_the_rows_whose_middles_it_passed() {
|
|
let rows = [px(40.0), px(60.0), px(40.0)];
|
|
assert_eq!(landing(&rows, px(100.0), px(110.0)), 0);
|
|
assert_eq!(landing(&rows, px(100.0), px(125.0)), 1);
|
|
assert_eq!(landing(&rows, px(100.0), px(175.0)), 2);
|
|
assert_eq!(landing(&rows, px(100.0), px(400.0)), 3);
|
|
}
|
|
|
|
#[test]
|
|
fn option_arrows_step_within_and_across_columns() {
|
|
let lens = [3, 0, 2];
|
|
assert_eq!(stepped(&lens, 0, 0, "up"), None);
|
|
assert_eq!(stepped(&lens, 0, 1, "down"), Some((0, 2)));
|
|
assert_eq!(stepped(&lens, 0, 2, "down"), None);
|
|
assert_eq!(stepped(&lens, 0, 2, "right"), Some((1, 0)));
|
|
assert_eq!(stepped(&lens, 2, 1, "left"), Some((1, 0)));
|
|
assert_eq!(stepped(&lens, 2, 1, "right"), None);
|
|
assert_eq!(stepped(&lens, 1, 0, "home"), None);
|
|
}
|
|
}
|