Skip to main content

CardGrid

Struct CardGrid 

Source
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>

Source

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.

Source

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.

Source

pub fn card_height(self, rows: u16) -> Self

Rows of content in every card, without the card’s padding.

Source

pub fn gap(self, columns: u16, rows: u16) -> Self

Cells between two cards of a row, and rows between two rows of cards.

Source

pub fn selected(self, index: Option<usize>) -> Self

The selected card’s index.

Source

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.

Source

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.

Source

pub fn scrollbar(self, style: ScrollbarStyle) -> Self

Draws the scrollbar in style whatever the theme chooses.

Source

pub fn on_select(self, message: impl Fn(usize) -> Msg + 'static) -> Self

Message for moving the selection to a card.

Source

pub fn on_activate(self, message: impl Fn(usize) -> Msg + 'static) -> Self

Message for opening a card (Enter, a click).

Source

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).

Source

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.

Source

pub fn context_menu( self, items: impl Fn(usize) -> Vec<ContextItem<Msg>> + 'static, ) -> Self

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.

Source§

impl<Msg: Clone + 'static> CardGrid<Msg>

Source

pub fn empty(self, state: EmptyState<Msg>) -> Self

What the grid shows when it has no cards, e.g. “No apps match” with a way out. Without it an empty grid draws nothing. Set the count with new first; a grid with cards leaves the empty state out.

Trait Implementations§

Source§

impl<Msg: Clone + 'static> Widget<Msg> for CardGrid<Msg>

Source§

fn measure(&self, cx: &mut MeasureCx<'_>, available: Size) -> Size

The size the widget wants when it may use up to available.
Source§

fn paint(&self, cx: &mut PaintCx<'_>, area: Rect)

Draws the widget into area.
Source§

fn paint_overlay(&self, cx: &mut PaintCx<'_>, anchor: Rect)

Draws the widget’s overlay after the whole view was painted, when it asked for one with PaintCx::request_overlay. anchor is the area the widget was painted in.
Source§

fn event(&self, cx: &mut EventCx<'_, Msg>, event: &Event) -> bool

Handles input. Returns true when the event was used; unused key and scroll events bubble to the parent widget.
Source§

fn focusable(&self) -> bool

Whether the widget can take keyboard focus.
Source§

fn children(&self) -> &[Node<Msg>]

Child nodes, for widgets that contain other widgets.
Source§

fn children_mut(&mut self) -> &mut [Node<Msg>]

Mutable child nodes, used to assign ids.

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>
where Option<Box<dyn Fn(usize) -> Msg>>: Freeze, Option<Box<dyn Fn(&mut View<'_, Msg>, usize)>>: Freeze, Option<Box<dyn Fn(usize) -> Vec<ContextItem<Msg>>>>: Freeze, Vec<Node<Msg>>: Freeze,

§

impl<Msg> Unpin for CardGrid<Msg>
where Option<Box<dyn Fn(usize) -> Msg>>: Unpin, Option<Box<dyn Fn(&mut View<'_, Msg>, usize)>>: Unpin, Option<Box<dyn Fn(usize) -> Vec<ContextItem<Msg>>>>: Unpin, Vec<Node<Msg>>: Unpin,

§

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> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.