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.
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; 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.
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,
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