flex-grow-0.1.0/.cargo_vcs_info.json0000644000000001360000000000100127440ustar { "git": { "sha1": "8d218bb537a38a5e2248df1d1e6829d5617b17ae" }, "path_in_vcs": "" }flex-grow-0.1.0/.gitignore000064400000000000000000000000101046102023000135130ustar 00000000000000/target flex-grow-0.1.0/Cargo.toml0000644000000016370000000000100107510ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.56" name = "flex-grow" version = "0.1.0" authors = ["Canop "] build = false autobins = false autoexamples = false autotests = false autobenches = false description = "allocate a size between children" readme = "README.md" keywords = ["flex"] categories = ["gui"] license = "MIT" repository = "https://github.com/Canop/flex-grow" [lib] name = "flex_grow" path = "src/lib.rs" [dependencies] flex-grow-0.1.0/Cargo.toml.orig000064400000000000000000000005711046102023000144260ustar 00000000000000[package] name = "flex-grow" version = "0.1.0" edition = "2021" authors = ["Canop "] description = "allocate a size between children" keywords = ["flex"] license = "MIT" categories = ["gui"] repository = "https://github.com/Canop/flex-grow" readme = "README.md" rust-version = "1.56" [dependencies] [workspace] members = [ "examples/flex-term", ] flex-grow-0.1.0/LICENSE000064400000000000000000000020461046102023000125430ustar 00000000000000MIT License Copyright (c) 2018 Canop Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. flex-grow-0.1.0/README.md000064400000000000000000000025021046102023000130120ustar 00000000000000[![MIT][s2]][l2] [![Latest Version][s1]][l1] [![docs][s3]][l3] [![Chat on Miaou][s4]][l4] [s1]: https://img.shields.io/crates/v/flex-grow.svg [l1]: https://crates.io/crates/flex-grow [s2]: https://img.shields.io/badge/license-MIT-blue.svg [l2]: LICENSE [s3]: https://docs.rs/flex-grow/badge.svg [l3]: https://docs.rs/flex-grow/ [s4]: https://miaou.dystroy.org/static/shields/room.svg [l4]: https://miaou.dystroy.org/3 Tiny utility computing the allocation of a size among "children". Typical use case: decide what columns to show in an UI, and what size to give to each column. Each child can have a min and max size, be optional with a priority, have a `grow` factor. Example: ``` use flex_grow::{Child, Container}; let container = Container::builder_in(50) .with_margin_between(1) .with(Child::new("name").clamp(5, 10)) .with(Child::new("price").with_size(8).optional_with_priority(7)) .with(Child::new("quantity").with_size(8).optional()) .with(Child::new("total").with_size(8)) .with(Child::new("comments").with_min(10).with_grow(2.0)) .with(Child::new("vendor").with_size(60).optional_with_priority(9)) .build() .unwrap(); assert_eq!(container.sizes(), vec![7, 8, 8, 8, 15, 0]); ``` You can give any argument to `Child::new`, it's stored in the child and returned by the `content()` method. flex-grow-0.1.0/src/lib.rs000064400000000000000000000202671046102023000134460ustar 00000000000000//! //! Tiny utility computing the allocation of a size among "children". //! //! Typical use case: decide what columns to show in an UI, and what size to give to each column. //! //! Each child can have a min and max size, be optional with a priority, have a `grow` factor. //! //! Example: //! //! ``` //! use flex_grow::{Child, Container}; //! //! let container = Container::builder_in(50) //! .with_margin_between(1) //! .with(Child::new("name").clamp(5, 10)) //! .with(Child::new("price").with_size(8).optional_with_priority(7)) //! .with(Child::new("quantity").with_size(8).optional()) //! .with(Child::new("total").with_size(8)) //! .with(Child::new("comments").with_min(10).with_grow(2.0)) //! .with(Child::new("vendor").with_size(60).optional_with_priority(9)) //! .build() //! .unwrap(); //! assert_eq!(container.sizes(), vec![7, 8, 8, 8, 15, 0]); //! ``` //! //! You can give any argument to `Child::new`, it's stored in the child and returned by the `content()` method. //! use std::fmt; pub struct ContainerBuilder { available: usize, margin_between: usize, children: Vec>, } #[derive(Debug, Clone, Copy, Default)] pub enum Optionality { #[default] Required, Optional { priority: usize, // bigger is more important }, } pub struct Child { content: C, constraints: ChildConstraints, size: Option, // None if not (yet) included } #[derive(Debug, Clone, Copy)] pub struct ChildConstraints { pub min: usize, pub max: Option, pub optionality: Optionality, pub grow: f64, } impl Default for ChildConstraints { fn default() -> Self { ChildConstraints { min: 0, max: None, optionality: Optionality::default(), grow: 1.0, } } } pub struct Container { pub children: Vec>, } #[derive(Debug, Clone)] pub enum Error { NotEnoughSpace, } impl std::error::Error for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Error::NotEnoughSpace => write!(f, "Not enough space"), } } } impl ChildConstraints {} impl ContainerBuilder { pub fn with_available(available: usize) -> Self { ContainerBuilder { available, children: Vec::new(), margin_between: 0, } } pub fn with_margin_between(mut self, margin: usize) -> Self { self.margin_between = margin; self } pub fn with(mut self, child: Child) -> Self { self.add(child); self } pub fn add(&mut self, child: Child) { self.children.push(child); } pub fn build(self) -> Result, Error> { let Self { mut available, mut children, margin_between, } = self; // first pass: we only add the required children. If their min size // is too big, we return an error. let mut added_children = 0; for child in &mut children { child.size = if child.is_optional() { None } else { let margin = if added_children > 0 { margin_between } else { 0 }; if child.constraints.min + margin > available { return Err(Error::NotEnoughSpace); } available -= child.constraints.min; available -= margin; added_children += 1; Some(child.constraints.min) }; } // second pass: we add the optional children until we run out of space, // by priority let mut optional_children = children .iter_mut() .filter(|c| c.is_optional()) .collect::>(); optional_children.sort_by_key(|c| { std::cmp::Reverse(match c.constraints.optionality { Optionality::Optional { priority } => priority, _ => 0, }) }); for child in optional_children { let margin = if added_children > 0 { margin_between } else { 0 }; if child.constraints.min + margin > available { continue; } available -= child.constraints.min; available -= margin; added_children += 1; child.size = Some(child.constraints.min); } // then we distribute the remaining space to the growable children let mut growths = vec![0.0; children.len()]; let mut sum_growths = 0.0; for (i, child) in children.iter().enumerate() { let Some(size) = child.size else { continue; }; growths[i] = child.constraints.grow * (match child.constraints.max { None => available, Some(max) => max - size, } as f64); sum_growths += growths[i]; } for i in 0..children.len() { let Some(size) = children[i].size else { continue; }; let growth = growths[i] as f64 * (available as f64 / sum_growths); available -= growth as usize; children[i].size = Some(size + growth as usize); } // Due to down rounding, it's probable that there's some available space left. while available > 0 { let mut given = 0; for child in &mut children { let Some(size) = child.size else { continue; }; if child.constraints.max.map_or(true, |max| size < max) { child.size = Some(size + 1); given += 1; available -= 1; if available == 0 { break; } } } if given == 0 { break; } } let con = Container { children }; Ok(con) } } impl Child { pub fn new(content: C) -> Self { let constraints = ChildConstraints::default(); Child { content, constraints, size: None, } } pub fn content(&self) -> &C { &self.content } pub fn optional(self) -> Self { self.optional_with_priority(0) } pub fn optional_with_priority(mut self, priority: usize) -> Self { self.constraints.optionality = Optionality::Optional { priority }; self } pub fn with_min(mut self, min: usize) -> Self { self.constraints.min = min; self } pub fn with_max(mut self, max: usize) -> Self { self.constraints.max = Some(max); self } pub fn clamp(mut self, min: usize, max: usize) -> Self { self.constraints.min = min; self.constraints.max = Some(max); self } pub fn with_size(mut self, size: usize) -> Self { self.constraints.min = size; self.constraints.max = Some(size); self } pub fn with_grow(mut self, grow: f64) -> Self { self.constraints.grow = grow; self } pub fn constraints(&self) -> ChildConstraints { self.constraints } fn is_optional(&self) -> bool { matches!(self.constraints.optionality, Optionality::Optional { .. }) } /// Return the size, if the child is included in the container, or none /// if there wasn't enough space to include it pub fn size(&self) -> Option { self.size } } impl Container { pub fn builder_in(available: usize) -> ContainerBuilder { ContainerBuilder::with_available(available) } /// Return the sizes of the children, in the order they were added, /// with 0 for non-included children pub fn sizes(&self) -> Vec { self.children .iter() .map(|sc| sc.size.unwrap_or(0)) .collect() } pub fn children(&self) -> &[Child] { &self.children } pub fn to_children(self) -> Vec> { self.children } }