Skip to content

Commit

Permalink
Merge pull request #443 from kas-gui/work
Browse files Browse the repository at this point in the history
Add CellCollection for use by Grid
  • Loading branch information
dhardy authored Feb 23, 2024
2 parents ab40e77 + f575171 commit 4f37071
Show file tree
Hide file tree
Showing 24 changed files with 673 additions and 465 deletions.
166 changes: 132 additions & 34 deletions crates/kas-core/src/core/collection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@

//! The [`Collection`] trait

use crate::layout::{GridCellInfo, GridDimensions};
use crate::{Layout, Node, Widget};
use kas_macros::impl_scope;
use std::ops::RangeBounds;

/// A collection of (child) widgets
Expand Down Expand Up @@ -114,59 +116,142 @@ pub trait Collection {
}
}

/// An iterator over a [`Collection`] as [`Layout`] elements
pub struct CollectionIterLayout<'a, C: Collection + ?Sized> {
start: usize,
end: usize,
collection: &'a C,
}
/// A collection with attached cell info
pub trait CellCollection: Collection {
/// Get row/column info associated with cell at `index`
fn cell_info(&self, index: usize) -> Option<GridCellInfo>;

impl<'a, C: Collection + ?Sized> Iterator for CollectionIterLayout<'a, C> {
type Item = &'a dyn Layout;
/// Iterate over [`GridCellInfo`] of elements within `range`
fn iter_cell_info(&self, range: impl RangeBounds<usize>) -> CollectionIterCellInfo<'_, Self> {
use std::ops::Bound::{Excluded, Included, Unbounded};
let start = match range.start_bound() {
Included(start) => *start,
Excluded(start) => *start + 1,
Unbounded => 0,
};
let end = match range.end_bound() {
Included(end) => *end + 1,
Excluded(end) => *end,
Unbounded => self.len(),
};
CollectionIterCellInfo {
start,
end,
collection: self,
}
}

fn next(&mut self) -> Option<Self::Item> {
let index = self.start;
if index < self.end {
self.start += 1;
self.collection.get_layout(index)
} else {
None
/// Get or calculate grid dimension info
///
/// The default implementation calculates this from [`Self::cell_info`].
fn grid_dimensions(&self) -> GridDimensions {
let mut dim = GridDimensions::default();
for cell_info in self.iter_cell_info(..) {
dim.cols = dim.cols.max(cell_info.col_end);
dim.rows = dim.rows.max(cell_info.row_end);
if cell_info.col_end - cell_info.col > 1 {
dim.col_spans += 1;
}
if cell_info.row_end - cell_info.row > 1 {
dim.row_spans += 1;
}
}
dim
}
}

impl<'a, C: Collection + ?Sized> DoubleEndedIterator for CollectionIterLayout<'a, C> {
fn next_back(&mut self) -> Option<Self::Item> {
if self.start < self.end {
let index = self.end - 1;
self.end = index;
self.collection.get_layout(index)
} else {
None
impl_scope! {
/// An iterator over a [`Collection`] as [`Layout`] elements
pub struct CollectionIterLayout<'a, C: Collection + ?Sized> {
start: usize,
end: usize,
collection: &'a C,
}

impl Iterator for Self {
type Item = &'a dyn Layout;

fn next(&mut self) -> Option<Self::Item> {
let index = self.start;
if index < self.end {
self.start += 1;
self.collection.get_layout(index)
} else {
None
}
}
}

impl DoubleEndedIterator for Self {
fn next_back(&mut self) -> Option<Self::Item> {
if self.start < self.end {
let index = self.end - 1;
self.end = index;
self.collection.get_layout(index)
} else {
None
}
}
}

impl ExactSizeIterator for Self {}
}

impl<'a, C: Collection + ?Sized> ExactSizeIterator for CollectionIterLayout<'a, C> {}
impl_scope! {
/// An iterator over a [`Collection`] as [`GridCellInfo`] elements
pub struct CollectionIterCellInfo<'a, C: CellCollection + ?Sized> {
start: usize,
end: usize,
collection: &'a C,
}

impl Iterator for Self {
type Item = GridCellInfo;

fn next(&mut self) -> Option<Self::Item> {
let index = self.start;
if index < self.end {
self.start += 1;
self.collection.cell_info(index)
} else {
None
}
}
}

impl DoubleEndedIterator for Self {
fn next_back(&mut self) -> Option<Self::Item> {
if self.start < self.end {
let index = self.end - 1;
self.end = index;
self.collection.cell_info(index)
} else {
None
}
}
}

impl ExactSizeIterator for Self {}
}

macro_rules! impl_slice {
(($($gg:tt)*) for $t:ty) => {
(($($gg:tt)*) for $t:ty as $w:ident in $pat:pat) => {
impl<$($gg)*> Collection for $t {
type Data = W::Data;

#[inline]
fn len(&self) -> usize {
<[W]>::len(self)
<[_]>::len(self)
}

#[inline]
fn get_layout(&self, index: usize) -> Option<&dyn Layout> {
self.get(index).map(|w| w as &dyn Layout)
self.get(index).map(|$pat| $w as &dyn Layout)
}

#[inline]
fn get_mut_layout(&mut self, index: usize) -> Option<&mut dyn Layout> {
self.get_mut(index).map(|w| w as &mut dyn Layout)
self.get_mut(index).map(|$pat| $w as &mut dyn Layout)
}

#[inline]
Expand All @@ -176,8 +261,8 @@ macro_rules! impl_slice {
index: usize,
closure: Box<dyn FnOnce(Node<'_>) + '_>,
) {
if let Some(w) = self.get_mut(index) {
closure(w.as_node(data));
if let Some($pat) = self.get_mut(index) {
closure($w.as_node(data));
}
}

Expand All @@ -186,7 +271,7 @@ macro_rules! impl_slice {
where
F: FnMut(&'a dyn Layout) -> std::cmp::Ordering,
{
Some(<[W]>::binary_search_by(self, move |w| f(w.as_layout())))
Some(<[_]>::binary_search_by(self, move |$pat| f($w.as_layout())))
}
}
};
Expand All @@ -195,6 +280,19 @@ macro_rules! impl_slice {
// NOTE: If Rust had better lifetime analysis we could replace
// the following impls with a single one:
// impl<W: Widget, T: std::ops::Deref<Target = [W]> + ?Sized> Collection for T
impl_slice!((const N: usize, W: Widget) for [W; N]);
impl_slice!((W: Widget) for [W]);
impl_slice!((W: Widget) for Vec<W>);
impl_slice!((const N: usize, W: Widget) for [W; N] as w in w);
impl_slice!((W: Widget) for [W] as w in w);
impl_slice!((W: Widget) for Vec<W> as w in w);

impl_slice!((const N: usize, W: Widget) for [(GridCellInfo, W); N] as w in (_, w));
impl_slice!((W: Widget) for [(GridCellInfo, W)] as w in (_, w));
impl_slice!((W: Widget) for Vec<(GridCellInfo, W)> as w in (_, w));

impl<W: Widget, C: Collection> CellCollection for C
where
C: std::ops::Deref<Target = [(GridCellInfo, W)]>,
{
fn cell_info(&self, index: usize) -> Option<GridCellInfo> {
self.get(index).map(|(info, _)| *info)
}
}
2 changes: 1 addition & 1 deletion crates/kas-core/src/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ mod widget_id;
#[cfg_attr(doc_cfg, doc(cfg(internal_doc)))]
pub mod impls;

pub use collection::Collection;
pub use collection::{CellCollection, Collection};
pub use data::*;
pub use layout::*;
pub use node::Node;
Expand Down
7 changes: 2 additions & 5 deletions crates/kas-core/src/decorations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,9 @@
//!
//! Note: due to definition in kas-core, some widgets must be duplicated.

use crate::event::{ConfigCx, CursorIcon, ResizeDirection};
use crate::geom::Rect;
use crate::layout::{Align, AxisInfo, SizeRules};
use crate::event::{CursorIcon, ResizeDirection};
use crate::text::Text;
use crate::theme::{DrawCx, SizeCx, TextClass};
use crate::Layout;
use crate::theme::TextClass;
use kas::prelude::*;
use kas::theme::MarkStyle;
use kas_macros::impl_scope;
Expand Down
4 changes: 2 additions & 2 deletions crates/kas-core/src/event/cx/cx_pub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

use std::fmt::Debug;
use std::future::IntoFuture;
use std::time::{Duration, Instant};
use std::time::Duration;

use super::*;
use crate::cast::Conv;
Expand All @@ -17,8 +17,8 @@ use crate::geom::{Offset, Vec2};
use crate::theme::{SizeCx, ThemeControl};
#[cfg(all(wayland_platform, feature = "clipboard"))]
use crate::util::warn_about_error;
use crate::{messages::Erased, Action, HasId, Id, Window, WindowId};
#[allow(unused)] use crate::{Events, Layout}; // for doc-links
use crate::{HasId, Window};

/// Public API
impl EventState {
Expand Down
2 changes: 1 addition & 1 deletion crates/kas-core/src/event/cx/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use crate::geom::Coord;
use crate::messages::{Erased, MessageStack};
use crate::util::WidgetHierarchy;
use crate::LayoutExt;
use crate::{Action, Id, NavAdvance, Node, Widget, WindowId};
use crate::{Action, Id, NavAdvance, Node, WindowId};

mod config;
mod cx_pub;
Expand Down
7 changes: 3 additions & 4 deletions crates/kas-core/src/event/cx/platform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@

//! Event manager — platform API

use smallvec::SmallVec;
use std::task::Poll;
use std::time::{Duration, Instant};
use std::time::Duration;

use super::*;
use crate::cast::traits::*;
use crate::geom::{Coord, DVec2};
use crate::geom::DVec2;
use crate::theme::ThemeSize;
use crate::{Action, Id, NavAdvance, Window};
use crate::Window;

// TODO: this should be configurable or derived from the system
const DOUBLE_CLICK_TIMEOUT: Duration = Duration::from_secs(1);
Expand Down
18 changes: 13 additions & 5 deletions crates/kas-core/src/layout/grid_solver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,23 @@ impl<T: Clone + Default> DefaultWithLen for Vec<T> {
/// Grid dimensions
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct GridDimensions {
/// The number of columns
///
/// This equals the maximum [`GridCellInfo::col_end`] of the colliection.
pub cols: u32,
/// The number of cells spanning more than one column
pub col_spans: u32,
/// The number of rows
///
/// This equals the maximum [`GridCellInfo::row_end`] of the colliection.
pub rows: u32,
/// The number of cells spanning more than one row
pub row_spans: u32,
}

/// Per-child information
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct GridChildInfo {
pub struct GridCellInfo {
/// Column index (first column when in a span)
pub col: u32,
/// One-past-last index of column span (`col_end = col + 1` without span)
Expand All @@ -53,10 +61,10 @@ pub struct GridChildInfo {
pub row_end: u32,
}

impl GridChildInfo {
impl GridCellInfo {
/// Construct from row and column
pub fn new(col: u32, row: u32) -> Self {
GridChildInfo {
GridCellInfo {
col,
col_end: col + 1,
row,
Expand Down Expand Up @@ -132,7 +140,7 @@ where
RSR: AsRef<[(SizeRules, u32, u32)]> + AsMut<[(SizeRules, u32, u32)]>,
{
type Storage = S;
type ChildInfo = GridChildInfo;
type ChildInfo = GridCellInfo;

fn for_child<CR: FnOnce(AxisInfo) -> SizeRules>(
&mut self,
Expand Down Expand Up @@ -285,7 +293,7 @@ impl<CT: RowTemp, RT: RowTemp, S: GridStorage> GridSetter<CT, RT, S> {

impl<CT: RowTemp, RT: RowTemp, S: GridStorage> RulesSetter for GridSetter<CT, RT, S> {
type Storage = S;
type ChildInfo = GridChildInfo;
type ChildInfo = GridCellInfo;

fn child_rect(&mut self, storage: &mut Self::Storage, info: Self::ChildInfo) -> Rect {
let x = self.w_offsets.as_mut()[usize::conv(info.col)];
Expand Down
2 changes: 1 addition & 1 deletion crates/kas-core/src/layout/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ use crate::dir::{Direction, Directional, Directions};
#[allow(unused)] use crate::Layout;

pub use align::{Align, AlignHints, AlignPair};
pub use grid_solver::{DefaultWithLen, GridChildInfo, GridDimensions, GridSetter, GridSolver};
pub use grid_solver::{DefaultWithLen, GridCellInfo, GridDimensions, GridSetter, GridSolver};
pub use row_solver::{RowPositionSolver, RowSetter, RowSolver};
pub use single_solver::{SingleSetter, SingleSolver};
pub use size_rules::SizeRules;
Expand Down
8 changes: 4 additions & 4 deletions crates/kas-core/src/layout/visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
#![allow(clippy::wrong_self_convention)]

use super::{AlignHints, AlignPair, AxisInfo, SizeRules};
use super::{GridChildInfo, GridDimensions, GridSetter, GridSolver, GridStorage};
use super::{GridCellInfo, GridDimensions, GridSetter, GridSolver, GridStorage};
use super::{RowSetter, RowSolver, RowStorage};
use super::{RulesSetter, RulesSolver};
use crate::draw::color::Rgb;
Expand Down Expand Up @@ -57,7 +57,7 @@ pub trait Visitable {
/// A list of [`Visitable`]
///
/// This is templated over `cell_info: C` where `C = ()` for lists or
/// `C = GridChildInfo` for grids.
/// `C = GridCellInfo` for grids.
#[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
#[cfg_attr(doc_cfg, doc(cfg(internal_doc)))]
pub trait VisitableList<C> {
Expand Down Expand Up @@ -169,7 +169,7 @@ impl<'a> Visitor<Box<dyn Visitable + 'a>> {
data: &'a mut S,
) -> Visitor<impl Visitable + 'a>
where
L: VisitableList<GridChildInfo> + 'a,
L: VisitableList<GridCellInfo> + 'a,
S: GridStorage,
{
Visitor(Grid {
Expand Down Expand Up @@ -569,7 +569,7 @@ struct Grid<'a, S, L> {

impl<'a, S: GridStorage, L> Visitable for Grid<'a, S, L>
where
L: VisitableList<GridChildInfo> + 'a,
L: VisitableList<GridCellInfo> + 'a,
{
fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules {
let mut solver = GridSolver::<Vec<_>, Vec<_>, _>::new(axis, self.dim, self.data);
Expand Down
Loading

0 comments on commit 4f37071

Please sign in to comment.