pub struct CardGrid<Msg> { /* private fields */ }Expand description
Cards laid out in as many columns as fit, for a store’s apps, a launcher’s programs or a choice of profiles. Only the cards on screen are built and drawn, so a grid of ten thousand cards costs what one screen of them costs.
Every card is a surface one step above its background, with no frame. Under the pointer a
card rises one tone with a soft pillar ▌ down its left edge; the selected card takes the
selected surface, and its pillar breathes while the grid has focus reached with the keyboard.
Nothing slides: a card is a surface, not a list row. Only one card is lit at a time: while
the pointer moves over the grid it carries the highlight and the selected card rests; the
next key goes on from the card the pointer is on.
The column count follows the width: cards are at least card_width’s
least width and share the room left, up to the widest. An area narrower than one card shows
one column as wide as the area, and the card’s content is cut there (build it with
Text::no_wrap so it ends in …).
The application owns the selection and the checked cards; the grid reports changes through messages. Keys while focused: arrows move between cards and stop at the edges (Right on the last card of a row stays there), Home and End go to the first and last card, PgUp and PgDn move a screen of rows, Enter activates, and Space toggles the check when checks are on, activating otherwise. A click selects and activates a card; with checks on, a click on the mark in a card’s top right corner only toggles it. The wheel scrolls a row of cards at a time, and the scrollbar can be pressed and dragged.
Four capabilities make the cards work the way the icons of a file explorer do, each off until
asked for: activate_on(Click::Double) selects on a click and activates
on a double click; multi_select selects several cards with Ctrl+click,
Shift+click and Space; box_select draws a box from the free space between
and after the cards and selects the cards it touches; droppable drags the
selection onto a card that takes it.
use std::rc::Rc;
use qframe::prelude::*;
use qframe::widgets::CardGrid;
struct Store {
apps: Rc<[(String, String)]>,
selected: Option<usize>,
}
#[derive(Clone)]
enum Msg {
Select(usize),
Open(usize),
}
impl App for Store {
type Msg = Msg;
fn update(&mut self, msg: Msg) -> Command<Msg> {
if let Msg::Select(index) | Msg::Open(index) = msg {
self.selected = Some(index);
}
Command::none()
}
fn view(&self, ui: &mut View<'_, Msg>) {
let apps = Rc::clone(&self.apps);
let grid = CardGrid::new(self.apps.len())
.card_width(24, 32)
.card_height(2)
.selected(self.selected)
.on_select(Msg::Select)
.on_activate(Msg::Open)
.card(move |ui, index| {
let (name, summary) = &apps[index];
ui.add(Text::new(name.as_str()).role("title").no_wrap());
ui.add(Text::new(summary.as_str()).role("secondary").no_wrap());
});
ui.add(grid).fill();
}
}
let apps: Rc<[(String, String)]> = (0..40).map(|n| (format!("App {n}"), "Does a thing".to_owned())).collect();
let mut store = Harness::new(Store { apps, selected: None }, 80, 10);
store.press("tab").press("right").press("right").press("down");
assert_eq!(store.app().selected, Some(4));Cards are built while the grid paints, once for each card on screen, by the closure given to
card. It lives as long as the widget, so it owns what it reads, e.g. an
Rc<[App]> cloned from the state. Widgets inside a card are drawn but take no input of their
own: the card is the pressable surface, and a press anywhere on it is the card’s. Idle
watches (View::on_idle) belong in the application’s own view, not in a card.
Style keys: card (bg, padding, pillar) with hover, selected, focus, pressed;
card-mark for a checked card’s mark and card-mark.off for the faint mark a lit card
offers while checks are on; tree-drop (bg) for the card a drag would drop on;
text-selection (bg) for the selection box; scrollbar.
Implementations§
Source§impl<Msg: 'static> CardGrid<Msg>
impl<Msg: 'static> CardGrid<Msg>
Sourcepub fn new(count: usize) -> Self
pub fn new(count: usize) -> Self
A grid of count cards, 24 to 32 cells wide and three rows of content high, two cells
apart in a row and one row apart between rows. Give it what cards show with
card.
Sourcepub fn card_width(self, min: u16, max: u16) -> Self
pub fn card_width(self, min: u16, max: u16) -> Self
The narrowest and the widest a card gets, in cells. As many columns as fit at min
share the width, each at most max.
Sourcepub fn card_height(self, rows: u16) -> Self
pub fn card_height(self, rows: u16) -> Self
Rows of content in every card, without the card’s padding.
Sourcepub fn gap(self, columns: u16, rows: u16) -> Self
pub fn gap(self, columns: u16, rows: u16) -> Self
Cells between two cards of a row, and rows between two rows of cards.
Sourcepub fn checked(self, checked: Vec<bool>) -> Self
pub fn checked(self, checked: Vec<bool>) -> Self
Turns checks on: checked[i] tells whether card i is checked, and a checked card
carries a mark in its top right corner. Space and a click on the mark report toggles
through on_toggle.
Sourcepub fn disabled(self, disabled: bool) -> Self
pub fn disabled(self, disabled: bool) -> Self
Keeps the grid from being hovered, focused or pressed; its messages are not sent. The cards fade and the selected card still shows.
Sourcepub fn scrollbar(self, style: ScrollbarStyle) -> Self
pub fn scrollbar(self, style: ScrollbarStyle) -> Self
Draws the scrollbar in style whatever the theme chooses.
Sourcepub fn on_select(self, message: impl Fn(usize) -> Msg + 'static) -> Self
pub fn on_select(self, message: impl Fn(usize) -> Msg + 'static) -> Self
Message for moving the selection to a card.
Sourcepub fn on_activate(self, message: impl Fn(usize) -> Msg + 'static) -> Self
pub fn on_activate(self, message: impl Fn(usize) -> Msg + 'static) -> Self
Message for opening a card (Enter, a click).
Sourcepub fn on_toggle(self, message: impl Fn(usize) -> Msg + 'static) -> Self
pub fn on_toggle(self, message: impl Fn(usize) -> Msg + 'static) -> Self
Message for checking or unchecking a card while checks are on (Space, a click on the mark).
Sourcepub fn card(self, build: impl Fn(&mut View<'_, Msg>, usize) + 'static) -> Self
pub fn card(self, build: impl Fn(&mut View<'_, Msg>, usize) + 'static) -> Self
Builds what card index shows, into the card’s padded content area. Called only for the
cards on screen, every time the grid paints.
Gives every card a context menu: items(index) builds the entries for the card of that
index, and the menu acts on the card it was opened on rather than on the selected one.
A right press on a card opens the menu at the pointer; the menu key or Shift+F10 opens the menu of the card the keys are on, scrolling it into view first. That card stays raised while the menu is open, so it is clear what the entries act on. A right press on a card that is not checked makes it the selection first, so a menu never acts on cards the person did not mean.
Sourcepub fn activate_on(self, click: Click) -> Self
pub fn activate_on(self, click: Click) -> Self
How many clicks activate a card: Click::Single, the default, selects and activates at
once; Click::Double only selects on a click and activates on a second press on the same
card within Click::INTERVAL. Enter activates either way.
Sourcepub fn multi_select(
self,
selected: &[usize],
message: impl Fn(Vec<usize>) -> Msg + 'static,
) -> Self
pub fn multi_select( self, selected: &[usize], message: impl Fn(Vec<usize>) -> Msg + 'static, ) -> Self
Lets several cards be selected at once: selected holds their indexes and
message(cards) asks the application to make cards the whole new selection.
The card given to selected stays the one the keys move from, while
every selected card takes the selected surface. Ctrl+click adds a card or takes it out,
Shift+click selects the cards from the last plain or Ctrl click to this one in reading
order, Space adds or takes out the card the keys are on and a plain click selects that one
card. A right click on a selected card keeps the selection for its menu; on another card it
makes that card the selection first.
Sourcepub fn box_select(self, on: bool) -> Self
pub fn box_select(self, on: bool) -> Self
Lets a drag from the free space between and after the cards draw a box: every card it
touches becomes the selection while it is drawn, or joins it when Ctrl was held at the
press, and a click there without a drag clears the selection. The box is a tone laid over
the cells it covers, never a frame. It needs multi_select and does
nothing without it.
Sourcepub fn droppable(
self,
message: impl Fn(RowDrop) -> Msg + 'static,
accepts: impl Fn(usize) -> bool + 'static,
) -> Self
pub fn droppable( self, message: impl Fn(RowDrop) -> Msg + 'static, accepts: impl Fn(usize) -> bool + 'static, ) -> Self
Lets cards be dragged onto other cards, such as files onto a folder: accepts(index) tells
whether a card takes drops and message(RowDrop) asks the application to move the cards.
A drag carries the pressed card, or the whole selection when it is
pressed on one of its cards; a click on a selected card without a drag makes it the one
selected card on release. The card under the pointer takes the accent tone while it can
take the drag. A release anywhere else, or on one of the dragged cards, does nothing. With
Click::Single a card activates on release rather than on press, so pressing a card to
drag it does not activate it.
Sourcepub fn on_copy_drop(self, message: impl Fn(RowDrop) -> Msg + 'static) -> Self
pub fn on_copy_drop(self, message: impl Fn(RowDrop) -> Msg + 'static) -> Self
A drop released with Ctrl held asks for a copy with message instead of the move of
droppable, the way a file explorer copies. A terminal that does not
report Ctrl with the pointer always moves. It does nothing without droppable.
Trait Implementations§
Source§impl<Msg: Clone + 'static> Widget<Msg> for CardGrid<Msg>
impl<Msg: Clone + 'static> Widget<Msg> for CardGrid<Msg>
Source§fn measure(&self, cx: &mut MeasureCx<'_>, available: Size) -> Size
fn measure(&self, cx: &mut MeasureCx<'_>, available: Size) -> Size
available.Source§fn paint_overlay(&self, cx: &mut PaintCx<'_>, anchor: Rect)
fn paint_overlay(&self, cx: &mut PaintCx<'_>, anchor: Rect)
PaintCx::request_overlay. anchor is the area the widget was painted in.Source§fn event(&self, cx: &mut EventCx<'_, Msg>, event: &Event) -> bool
fn event(&self, cx: &mut EventCx<'_, Msg>, event: &Event) -> bool
true when the event was used; unused key and scroll events
bubble to the parent widget.Source§fn children_mut(&mut self) -> &mut [Node<Msg>]
fn children_mut(&mut self) -> &mut [Node<Msg>]
Auto Trait Implementations§
impl<Msg> !RefUnwindSafe for CardGrid<Msg>
impl<Msg> !Send for CardGrid<Msg>
impl<Msg> !Sync for CardGrid<Msg>
impl<Msg> !UnwindSafe for CardGrid<Msg>
impl<Msg> Freeze for CardGrid<Msg>
impl<Msg> Unpin for CardGrid<Msg>
impl<Msg> UnsafeUnpin for CardGrid<Msg>where
Option<Box<dyn Fn(usize) -> Msg>>: UnsafeUnpin,
Option<Box<dyn Fn(&mut View<'_, Msg>, usize)>>: UnsafeUnpin,
Option<Box<dyn Fn(usize) -> Vec<ContextItem<Msg>>>>: UnsafeUnpin,
Vec<Node<Msg>>: UnsafeUnpin,
Picking<Msg>: UnsafeUnpin,
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more