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.

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, Shift+arrows, Ctrl+A 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>

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

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.

Source

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; Shift with the arrows, PgUp/PgDn or Home/End extends that range the same way, Ctrl+A selects every card, Space adds or takes out the card the keys are on, Esc reduces several selected cards to that one, and a plain click or arrow 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.

Source

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.

Source

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.

Source

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.

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