Skip to main content

GridLayout

Struct GridLayout 

Source
#[non_exhaustive]
pub struct GridLayout { /* private fields */ }
Expand description

A saved grid layout: the columns by ColumnId in display order, each with its width, visibility and zone, the sort, and the grouping with the expansion of its groups.

Plain data without a GPUI entity and without the application’s column type, so one value fits every grid of the same view. Serialize it with serde or a LayoutCodec. The grid resolves it against its schema when it is applied: unknown columns are ignored and new ones are added.

The format is version 1 (GridLayout::VERSION). Defaults are not written: no view_id when there is none, no visible for a visible column, no frozen for a scrolling one, no sort or group_by when it is empty, and no expansion when every group is expanded and none is toggled. Reading ignores unknown fields, so a newer build can add an optional field without a new version, as group_by and expansion were added; an older build drops that field when it saves again.

Deserializing with serde keeps the version the data carries. The codec refuses a version this build does not read, and so does the grid when it applies a layout.

use gpui_kit_grid::{
    ColumnZone, GridLayout, JsonLayoutCodec, LayoutCodec, LayoutColumn, LayoutSort,
    SortDirection,
};

let layout = GridLayout::new()
    .with_view_id("trades")
    .with_column(LayoutColumn::new("id", 80.).with_zone(ColumnZone::FrozenLeft))
    .with_column(LayoutColumn::new("price", 100.))
    .with_column(LayoutColumn::new("fee", 90.).with_visible(false))
    .with_sort([LayoutSort::new("price", SortDirection::Descending)]);

let codec = JsonLayoutCodec::default();
let text = codec.encode(&layout)?;
assert_eq!(
    text,
    concat!(
        r#"{"version":1,"view_id":"trades","columns":["#,
        r#"{"id":"id","width":80.0,"frozen":"left"},"#,
        r#"{"id":"price","width":100.0},"#,
        r#"{"id":"fee","width":90.0,"visible":false}],"#,
        r#""sort":[{"column":"price","direction":"descending"}]}"#,
    )
);
assert_eq!(codec.decode(&text)?, layout);

§Grouping and expansion

group_by names the grouping levels by column, the top level first. A layout without levels has no grouping: applying it removes the grid’s grouping. expansion keeps the default depth and the groups the user toggled against it, each as its key values from the top level down; a value is never an index, and the levels of the same layout name its columns.

use gpui_kit_grid::{
    ExpandDepth, GridLayout, JsonLayoutCodec, LayoutCodec, LayoutExpansion, LayoutGroup,
    OwnedValue, SortDirection,
};

let aapl = OwnedValue::Text("AAPL".into());
let layout = GridLayout::new()
    .with_group_by([
        LayoutGroup::new("instrument", SortDirection::Ascending),
        LayoutGroup::new("side", SortDirection::Descending),
    ])
    .with_expansion(LayoutExpansion::new(ExpandDepth::All).with_toggled([aapl]));

let codec = JsonLayoutCodec::default();
let text = codec.encode(&layout)?;
assert_eq!(
    text,
    concat!(
        r#"{"version":1,"columns":[],"group_by":["#,
        r#"{"column":"instrument","direction":"ascending"},"#,
        r#"{"column":"side","direction":"descending"}],"#,
        r#""expansion":{"depth":"all","toggled":[["AAPL"]]}}"#,
    )
);
assert_eq!(codec.decode(&text)?, layout);

§Saving a grid in a dock panel

The grid does no I/O and does not depend on the dock. A panel hands its grid’s layout to the dock in Panel::dump, and the panel’s builder in the registry (register_panel) gives it back to with_layout. LiveGridState::layout reads state only, so a panel in a tab that was never shown saves its layout too. The complete, tested version is crates/grid/tests/dock.rs.

ⓘ
const TRADES: &str = "trades";

impl TradesPanel {
    fn new(layout: Option<GridLayout>, cx: &mut Context<Self>) -> Self {
        let grid = cx.new(|cx| {
            let grid = LiveGridState::new_standalone(schema(), Trades, StoreOptions::default(), cx)
                .with_view_id(TRADES);
            match layout {
                Some(layout) => grid.with_layout(layout),
                None => grid,
            }
        });
        Self { grid, focus_handle: cx.focus_handle() }
    }
}

impl BasePanel for TradesPanel {
    fn panel_name(&self) -> &'static str {
        TRADES
    }

    /// The dock pulls the layout when it saves; the grid needs no render.
    fn dump(&self, cx: &App) -> PanelState {
        let mut state = PanelState::new(TRADES);
        let layout = self.grid.read(cx).layout();
        state.info = PanelInfo::panel(serde_json::json!({ "grid": layout }));
        state
    }
}

// At startup, before `DockArea::load`. A bad layout opens the panel with
// the schema's layout instead of failing the load; `with_layout` ignores
// a layout of a newer version.
register_panel(cx, TRADES, |context, _window, cx| {
    let layout = match context.info() {
        PanelInfo::Panel(value) => value
            .get("grid")
            .and_then(|grid| serde_json::from_value::<GridLayout>(grid.clone()).ok()),
        _ => None,
    };
    panel_handle(cx.new(|cx| TradesPanel::new(layout, cx)))
});

The dock saves on its own edits only (DockEvent::LayoutChanged), so changes inside a panel must trigger a save too. They are the grid’s LayoutChange, SortChange, GroupChange and ExpansionChange events, which come at once, once per finished gesture or call. The application delays the write (0.5–1 s), since groups are expanded and collapsed as often as a chevron is clicked, and also writes when it quits.

ⓘ
cx.subscribe(&grid, |app, _, event: &LiveGridEvent<u64, TradeColumn>, cx| {
    if matches!(
        event,
        LiveGridEvent::LayoutChange { .. }
            | LiveGridEvent::SortChange { .. }
            | LiveGridEvent::GroupChange { .. }
            | LiveGridEvent::ExpansionChange { .. }
    ) {
        app.schedule_save(cx);
    }
})
.detach();

Implementations§

Source§

impl GridLayout

Source

pub const VERSION: u32 = 1

The format version this build writes and the newest it reads.

Source

pub fn new() -> Self

An empty layout of the current version, without a view id.

Source

pub fn with_view_id(self, view_id: impl Into<String>) -> Self

Names the view the layout belongs to (“trades”). A grid with another view id refuses the layout.

Source

pub fn with_column(self, column: LayoutColumn) -> Self

Appends a column; the order of columns is the display order.

Source

pub fn with_sort(self, sort: impl IntoIterator<Item = LayoutSort>) -> Self

Replaces the sort; the first key sorts first.

Source

pub fn with_group_by( self, levels: impl IntoIterator<Item = LayoutGroup>, ) -> Self

Replaces the grouping levels; the first level groups first. No levels is no grouping.

Source

pub fn with_expansion(self, expansion: LayoutExpansion) -> Self

Replaces the expansion of groups. The default, every group expanded and none toggled, is not written.

Source

pub fn version(&self) -> u32

The format version the layout was written in.

Source

pub fn view_id(&self) -> Option<&str>

The view the layout belongs to, if it names one.

Source

pub fn columns(&self) -> &[LayoutColumn]

The columns in display order, as saved: they may include columns the schema no longer has, and repeats.

Source

pub fn column(&self, id: &str) -> Option<&LayoutColumn>

The first saved column with this id.

Source

pub fn sort(&self) -> &[LayoutSort]

The sort keys, first key first.

Source

pub fn group_by(&self) -> &[LayoutGroup]

The grouping levels, the top level first, as saved: they may name columns the schema no longer has or cannot group, and repeats.

Source

pub fn expansion(&self) -> Option<&LayoutExpansion>

The expansion of groups; None when the layout keeps the default: every group expanded and none toggled.

Trait Implementations§

Source§

impl Clone for GridLayout

Source§

fn clone(&self) -> GridLayout

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for GridLayout

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for GridLayout

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl PartialEq for GridLayout

Source§

fn eq(&self, other: &GridLayout) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl Serialize for GridLayout

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more
Source§

impl StructuralPartialEq for GridLayout

Auto Trait Implementations§

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> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
§

impl<T> Downcast<T> for T

§

fn downcast(&self) -> &T

§

impl<T> Downcast for T
where T: Any,

§

fn into_any(self: Box<T>) -> Box<dyn Any>

Convert Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.
§

fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>

Convert Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be further downcast into Rc<ConcreteType> where ConcreteType implements Trait.
§

fn as_any(&self) -> &(dyn Any + 'static)

Convert &Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &Any’s vtable from &Trait’s.
§

fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)

Convert &mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &mut Any’s vtable from &mut Trait’s.
§

impl<T> DowncastSync for T
where T: Any + Send + Sync,

§

fn into_any_arc(self: Arc<T>) -> Arc<dyn Any + Sync + Send>

Convert Arc<Trait> (where Trait: Downcast) to Arc<Any>. Arc<Any> can then be further downcast into Arc<ConcreteType> where ConcreteType implements Trait.
Source§

impl<T> DynClone for T
where T: Clone,

Source§

fn __clone_box(&self, _: Private) -> *mut ()

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
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
§

impl<T> Pointable for T

§

const ALIGN: usize

The alignment of pointer.
§

type Init = T

The type for initializers.
§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> Serialize for T
where T: Serialize + ?Sized,

Source§

fn erased_serialize(&self, serializer: &mut dyn Serializer) -> Result<(), Error>

Source§

fn do_erased_serialize( &self, serializer: &mut dyn Serializer, ) -> Result<(), ErrorImpl>

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

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

Source§

type Error = Infallible

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

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

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

impl<T> Upcast<T> for T

§

fn upcast(&self) -> Option<&T>

§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

§

impl<T> WasmNotSend for T
where T: Send,

§

impl<T> WasmNotSendSync for T
where T: WasmNotSend + WasmNotSync,

§

impl<T> WasmNotSync for T
where T: Sync,