summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorClementTsang <cjhtsang@uwaterloo.ca>2021-08-31 17:19:05 -0400
committerClementTsang <cjhtsang@uwaterloo.ca>2021-09-05 19:09:11 -0400
commiteddc9a16c7ac1be90144e8a247b13d6fd96238cf (patch)
tree8d197acfebf39a02cd8cc4abd0770f4508cbae6f
parent204b4dc3510b70618741f82a1e2df6c17c6dadda (diff)
refactor: move basic mode over
Because writing your own layout system and management is just *so much fun*. Totally. ------------------------------------------------------------------- Moves the basic mode system over to the new drawing/widget system. In the process, it has forced me to completely redo how we do layouts... again. This is because basic mode has widgets that control their own height - this means the height of the columns and rows that wrap it are also affected by the widget's height. The previous system, using a constraint tree and splitting draw Rects via tui-rs' built-in constraint solver, did not support this concept very well. It was not simple to propagate up the widths/heights towards parents while also using tui-rs' built-in constraint solver. In the end, it was easier to just rewrite it using another algorithm. We now follow a process very similar to Flutter's layout system. Relevant links to the Flutter docs are found in the code or below: - https://flutter.dev/docs/development/ui/layout/constraints - https://flutter.dev/docs/resources/inside-flutter#sublinear-layouts The gist of it, however, is that we now instead a few new options for any element in the layout tree. A node can either: - Grow to fill remaining space - Take up as much room as its children - Be a specific length Technically right now, it's not perfect, in that leaf nodes can be as large as their children (which makes no sense), though in that case it just treats it as an expand.
-rw-r--r--src/app.rs31
-rw-r--r--src/app/layout_manager.rs657
-rw-r--r--src/app/widgets.rs58
-rw-r--r--src/app/widgets/base.rs3
-rw-r--r--src/app/widgets/base/carousel.rs42
-rw-r--r--src/app/widgets/base/text_table.rs3
-rw-r--r--src/app/widgets/basic_cpu.rs0
-rw-r--r--src/app/widgets/basic_mem.rs0
-rw-r--r--src/app/widgets/basic_net.rs0
-rw-r--r--src/app/widgets/bottom_widgets.rs35
-rw-r--r--src/app/widgets/bottom_widgets/basic_cpu.rs205
-rw-r--r--src/app/widgets/bottom_widgets/basic_mem.rs164
-rw-r--r--src/app/widgets/bottom_widgets/basic_net.rs134
-rw-r--r--src/app/widgets/bottom_widgets/battery.rs (renamed from src/app/widgets/battery.rs)58
-rw-r--r--src/app/widgets/bottom_widgets/carousel.rs209
-rw-r--r--src/app/widgets/bottom_widgets/cpu.rs (renamed from src/app/widgets/cpu.rs)34
-rw-r--r--src/app/widgets/bottom_widgets/disk.rs (renamed from src/app/widgets/disk.rs)51
-rw-r--r--src/app/widgets/bottom_widgets/empty.rs58
-rw-r--r--src/app/widgets/bottom_widgets/mem.rs (renamed from src/app/widgets/mem.rs)28
-rw-r--r--src/app/widgets/bottom_widgets/net.rs (renamed from src/app/widgets/net.rs)53
-rw-r--r--src/app/widgets/bottom_widgets/process.rs (renamed from src/app/widgets/process.rs)50
-rw-r--r--src/app/widgets/bottom_widgets/temp.rs (renamed from src/app/widgets/temp.rs)46
-rw-r--r--src/app/widgets/tui_widgets.rs3
-rw-r--r--src/app/widgets/tui_widgets/pipe_gauge.rs138
-rw-r--r--src/bin/main.rs7
-rw-r--r--src/canvas.rs170
-rw-r--r--src/canvas/drawing.rs4
-rw-r--r--src/canvas/drawing/cpu_graph.rs288
-rw-r--r--src/canvas/drawing_utils.rs149
-rw-r--r--src/constants.rs52
-rw-r--r--src/options.rs142
-rw-r--r--src/options/layout_options.rs643
-rw-r--r--tests/layout_management_tests.rs468
-rw-r--r--tests/layout_movement_tests.rs10
34 files changed, 2591 insertions, 1402 deletions
diff --git a/src/app.rs b/src/app.rs
index 91b243f3..54dade87 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -282,8 +282,12 @@ impl AppState {
}
KeyCode::Char('q') => EventResult::Quit,
KeyCode::Char('e') => {
- self.is_expanded = !self.is_expanded;
- EventResult::Redraw
+ if self.app_config_fields.use_basic_mode {
+ EventResult::NoRedraw
+ } else {
+ self.is_expanded = !self.is_expanded;
+ EventResult::Redraw
+ }
}
KeyCode::Char('?') => {
self.help_dialog_state.is_showing_help = true;
@@ -353,14 +357,29 @@ impl AppState {
} else {
for (id, widget) in self.widget_lookup_map.iter_mut() {
if widget.does_border_intersect_mouse(&event) {
- let was_id_already_selected = self.selected_widget == *id;
- self.selected_widget = *id;
-
let result = widget.handle_mouse_event(event);
+
+ let new_id;
+ match widget.selectable_type() {
+ SelectableType::Selectable => {
+ new_id = *id;
+ }
+ SelectableType::Unselectable => {
+ let result = widget.handle_mouse_event(event);
+ return self.convert_widget_event_result(result);
+ }
+ SelectableType::Redirect(redirected_id) => {
+ new_id = redirected_id;
+ }
+ }
+
+ let was_id_already_selected = self.selected_widget == new_id;
+ self.selected_widget = new_id;
+
if was_id_already_selected {
return self.convert_widget_event_result(result);
} else {
- // If the aren't equal, *force* a redraw.
+ // If the weren't equal, *force* a redraw.
let _ = self.convert_widget_event_result(result);
return EventResult::Redraw;
}
diff --git a/src/app/layout_manager.rs b/src/app/layout_manager.rs
index 7105fa73..672fea19 100644
--- a/src/app/layout_manager.rs
+++ b/src/app/layout_manager.rs
@@ -1,12 +1,18 @@
use crate::{
- app::{DiskTable, MemGraph, NetGraph, OldNetGraph, ProcessManager, TempTable},
+ app::{
+ BasicCpu, BasicMem, BasicNet, BatteryTable, Carousel, DiskTable, Empty, MemGraph, NetGraph,
+ OldNetGraph, ProcessManager, TempTable,
+ },
error::{BottomError, Result},
- options::layout_options::{Row, RowChildren},
+ options::{
+ layout_options::{LayoutRule, Row, RowChildren},
+ ProcessDefaults,
+ },
};
use fxhash::FxHashMap;
use indextree::{Arena, NodeId};
-use std::collections::BTreeMap;
-use tui::layout::Constraint;
+use std::{cmp::min, collections::BTreeMap};
+use tui::layout::Rect;
use typed_builder::*;
use crate::app::widgets::Widget;
@@ -910,6 +916,7 @@ pub enum BottomWidgetType {
BasicNet,
BasicTables,
Battery,
+ Carousel,
}
impl BottomWidgetType {
@@ -958,6 +965,9 @@ impl std::str::FromStr for BottomWidgetType {
"disk" => Ok(BottomWidgetType::Disk),
"empty" => Ok(BottomWidgetType::Empty),
"battery" | "batt" => Ok(BottomWidgetType::Battery),
+ "bcpu" => Ok(BottomWidgetType::BasicCpu),
+ "bmem" => Ok(BottomWidgetType::BasicMem),
+ "bnet" => Ok(BottomWidgetType::BasicNet),
_ => Err(BottomError::ConfigError(format!(
"\"{}\" is an invalid widget name.
@@ -987,43 +997,73 @@ Supported widget names:
// --- New stuff ---
/// Represents a row in the layout tree.
-#[derive(PartialEq, Eq, Default)]
+#[derive(PartialEq, Eq, Clone)]
pub struct RowLayout {
last_selected_index: usize,
- pub constraints: Vec<Constraint>,
+ pub parent_rule: LayoutRule,
+ pub bound: Rect,
+}
+
+impl RowLayout {
+ fn new(parent_rule: LayoutRule) -> Self {
+ Self {
+ last_selected_index: 0,
+ parent_rule,
+ bound: Rect::default(),
+ }
+ }
}
/// Represents a column in the layout tree.
-#[derive(PartialEq, Eq, Default)]
+#[derive(PartialEq, Eq, Clone)]
pub struct ColLayout {
last_selected_index: usize,
- pub constraints: Vec<Constraint>,
+ pub parent_rule: LayoutRule,
+ pub bound: Rect,
+}
+
+impl ColLayout {
+ fn new(parent_rule: LayoutRule) -> Self {
+ Self {
+ last_selected_index: 0,
+ parent_rule,
+ bound: Rect::default(),
+ }
+ }
+}
+
+/// Represents a widget in the layout tree.
+#[derive(PartialEq, Eq, Clone, Default)]
+pub struct WidgetLayout {
+ pub bound: Rect,
}
/// A [`LayoutNode`] represents a single node in the overall widget hierarchy. Each node is one of:
/// - [`LayoutNode::Row`] (a non-leaf that distributes its children horizontally)
/// - [`LayoutNode::Col`] (a non-leaf node that distributes its children vertically)
/// - [`LayoutNode::Widget`] (a leaf node that contains the ID of the widget it is associated with)
-#[derive(PartialEq, Eq)]
+#[derive(PartialEq, Eq, Clone)]
pub enum LayoutNode {
/// A non-leaf that distributes its children horizontally
Row(RowLayout),
/// A non-leaf node that distributes its children vertically
Col(ColLayout),
/// A leaf node that contains the ID of the widget it is associated with
- Widget,
+ Widget(WidgetLayout),
}
impl LayoutNode {
- pub fn set_constraints(&mut self, constraints: Vec<Constraint>) {
+ fn set_bound(&mut self, bound: Rect) {
match self {
LayoutNode::Row(row) => {
- row.constraints = constraints;
+ row.bound = bound;
}
LayoutNode::Col(col) => {
- col.constraints = constraints;
+ col.bound = bound;
+ }
+ LayoutNode::Widget(widget) => {
+ widget.bound = bound;
}
- LayoutNode::Widget => {}
}
}
}
@@ -1050,86 +1090,140 @@ pub struct LayoutCreationOutput {
/// selected [`NodeId`].
// FIXME: This is currently jury-rigged "glue" just to work with the existing config system! We are NOT keeping it like this, it's too awful to keep like this!
pub fn create_layout_tree(
- rows: &[Row], process_defaults: crate::options::ProcessDefaults,
- app_config_fields: &AppConfigFields,
+ rows: &[Row], process_defaults: ProcessDefaults, app_config_fields: &AppConfigFields,
) -> Result<LayoutCreationOutput> {
fn add_widget_to_map(
widget_lookup_map: &mut FxHashMap<NodeId, TmpBottomWidget>, widget_type: BottomWidgetType,
- widget_id: NodeId, process_defaults: &crate::options::ProcessDefaults,
- app_config_fields: &AppConfigFields,
+ widget_id: NodeId, process_defaults: &ProcessDefaults, app_config_fields: &AppConfigFields,
+ width: LayoutRule, height: LayoutRule,
) -> Result<()> {
match widget_type {
BottomWidgetType::Cpu => {
- widget_lookup_map
- .insert(widget_id, CpuGraph::from_config(app_config_fields).into());
+ widget_lookup_map.insert(
+ widget_id,
+ CpuGraph::from_config(app_config_fields)
+ .width(width)
+ .height(height)
+ .into(),
+ );
}
BottomWidgetType::Mem => {
let graph = TimeGraph::from_config(app_config_fields);
- widget_lookup_map.insert(widget_id, MemGraph::new(graph).into());
+ widget_lookup_map.insert(
+ widget_id,
+ MemGraph::new(graph).width(width).height(height).into(),
+ );
}
BottomWidgetType::Net => {
if app_config_fields.use_old_network_legend {
widget_lookup_map.insert(
widget_id,
- OldNetGraph::from_config(app_config_fields).into(),
+ OldNetGraph::from_config(app_config_fields)
+ .width(width)
+ .height(height)
+ .into(),
);
} else {
- widget_lookup_map
- .insert(widget_id, NetGraph::from_config(app_config_fields).into());
+ widget_lookup_map.insert(
+ widget_id,
+ NetGraph::from_config(app_config_fields)
+ .width(width)
+ .height(height)
+ .into(),
+ );
}
}
BottomWidgetType::Proc => {
- widget_lookup_map.insert(widget_id, ProcessManager::new(process_defaults).into());
+ widget_lookup_map.insert(
+ widget_id,
+ ProcessManager::new(process_defaults)
+ .width(width)
+ .height(height)
+ .basic_mode(app_config_fields.use_basic_mode)
+ .into(),
+ );
}
BottomWidgetType::Temp => {
widget_lookup_map.insert(
widget_id,
TempTable::default()
.set_temp_type(app_config_fields.temperature_type.clone())
+ .width(width)
+ .height(height)
+ .basic_mode(app_config_fields.use_basic_mode)
.into(),
);
}
BottomWidgetType::Disk => {
- widget_lookup_map.insert(widget_id, DiskTable::default().into());
+ widget_lookup_map.insert(
+ widget_id,
+ DiskTable::default()
+ .width(width)
+ .height(height)
+ .basic_mode(app_config_fields.use_basic_mode)
+ .into(),
+ );
+ }
+ BottomWidgetType::Battery => {
+ widget_lookup_map.insert(
+ widget_id,
+ BatteryTable::default()
+ .width(width)
+ .height(height)
+ .basic_mode(app_config_fields.use_basic_mode)
+ .into(),
+ );
+ }
+ BottomWidgetType::BasicCpu => {
+ widget_lookup_map.insert(
+ widget_id,
+ BasicCpu::from_config(app_config_fields).width(width).into(),
+ );
+ }
+ BottomWidgetType::BasicMem => {
+ widget_lookup_map.insert(widget_id, BasicMem::default().width(width).into());
+ }
+ BottomWidgetType::BasicNet => {
+ widget_lookup_map.insert(
+ widget_id,
+ BasicNet::from_config(app_config_fields).width(width).into(),
+ );
+ }
+ BottomWidgetType::Empty => {
+ widget_lookup_map.insert(
+ widget_id,
+ Empty::default().width(width).height(height).into(),
+ );
}
- BottomWidgetType::Battery => {}
_ => {}
}
Ok(())
}
- let mut layout_tree = Arena::new();
- let root_id = layout_tree.new_node(LayoutNode::Col(ColLayout::default()));
+ let mut arena = Arena::new();
+ let root_id = arena.new_node(LayoutNode::Col(ColLayout::new(LayoutRule::Expand {
+ ratio: 1,
+ })));
let mut widget_lookup_map = FxHashMap::default();
let mut first_selected = None;
- let mut first_widget_seen = None; // Backup
+ let mut first_widget_seen = None; // Backup selected widget
let mut used_widgets = UsedWidgets::default();
- let row_sum: u32 = rows.iter().map(|row| row.ratio.unwrap_or(1)).sum();
- let mut root_constraints = Vec::with_capacity(rows.len());
for row in rows {
- root_constraints.push(Constraint::Ratio(row.ratio.unwrap_or(1), row_sum));
- let layout_node = LayoutNode::Row(RowLayout::default());
- let row_id = layout_tree.new_node(layout_node);
- root_id.append(row_id, &mut layout_tree);
-
- if let Some(cols) = &row.child {
- let mut row_constraints = Vec::with_capacity(cols.len());
- let col_sum: u32 = cols
- .iter()
- .map(|col| match col {
- RowChildren::Widget(widget) => widget.ratio.unwrap_or(1),
- RowChildren::Col { ratio, child: _ } => ratio.unwrap_or(1),
- })
- .sum();
-
- for col in cols {
- match col {
+ let row_id = arena.new_node(LayoutNode::Row(RowLayout::new(
+ row.ratio
+ .map(|ratio| LayoutRule::Expand { ratio })
+ .unwrap_or(LayoutRule::Child),
+ )));
+ root_id.append(row_id, &mut arena);
+
+ if let Some(children) = &row.child {
+ for child in children {
+ match child {
RowChildren::Widget(widget) => {
- row_constraints.push(Constraint::Ratio(widget.ratio.unwrap_or(1), col_sum));
- let widget_id = layout_tree.new_node(LayoutNode::Widget);
- row_id.append(widget_id, &mut layout_tree);
+ let widget_id = arena.new_node(LayoutNode::Widget(WidgetLayout::default()));
+ row_id.append(widget_id, &mut arena);
if let Some(true) = widget.default {
first_selected = Some(widget_id);
@@ -1148,28 +1242,108 @@ pub fn create_layout_tree(
widget_id,
&process_defaults,
app_config_fields,
+ widget.rule.unwrap_or_default(),
+ LayoutRule::default(),
)?;
}
+ RowChildren::Carousel {
+ carousel_children,
+ default,
+ } => {
+ if !carousel_children.is_empty() {
+ let mut child_ids = Vec::with_capacity(carousel_children.len());
+ let carousel_widget_id =
+ arena.new_node(LayoutNode::Widget(WidgetLayout::default()));
+ row_id.append(carousel_widget_id, &mut arena);
+
+ // Add the first widget as a default widget if needed.
+ {
+ let widget_id =
+ arena.new_node(LayoutNode::Widget(WidgetLayout::default()));
+ carousel_widget_id.append(widget_id, &mut arena);
+
+ let widget_type =
+ carousel_children[0].parse::<BottomWidgetType>()?;
+ used_widgets.add(&widget_type);
+
+ if let Some(true) = default {
+ first_selected = Some(widget_id);
+ }
+
+ if first_widget_seen.is_none() {
+ first_widget_seen = Some(widget_id);
+ }
+
+ add_widget_to_map(
+ &mut widget_lookup_map,
+ widget_type,
+ widget_id,
+ &process_defaults,
+ app_config_fields,
+ LayoutRule::default(),
+ LayoutRule::default(),
+ )?;
+
+ child_ids.push(widget_id);
+ }
+
+ // Handle the rest of the children.
+ for child in carousel_children[1..].iter() {
+ let widget_id =
+ arena.new_node(LayoutNode::Widget(WidgetLayout::default()));
+ carousel_widget_id.append(widget_id, &mut arena);
+
+ let widget_type = child.parse::<BottomWidgetType>()?;
+ used_widgets.add(&widget_type);
+
+ add_widget_to_map(
+ &mut widget_lookup_map,
+ widget_type,
+ widget_id,
+ &process_defaults,
+ app_config_fields,
+ LayoutRule::default(),
+ LayoutRule::default(),
+ )?;
+
+ child_ids.push(widget_id);
+ }
+
+ widget_lookup_map.insert(
+ carousel_widget_id,
+ Carousel::new(
+ child_ids
+ .into_iter()
+ .filter_map(|child_id| {
+ if let Some(w) = widget_lookup_map.get(&child_id) {
+ Some((child_id, w.get_pretty_name().into()))
+ } else {
+ None
+ }
+ })
+ .collect(),
+ )
+ .into(),
+ );
+ }
+ }
RowChildren::Col {
ratio,
- child: children,
+ child: col_child,
} => {
- row_constraints.push(Constraint::Ratio(ratio.unwrap_or(1), col_sum));
- let col_node = LayoutNode::Col(ColLayout::default());
- let col_id = layout_tree.new_node(col_node);
- row_id.append(col_id, &mut layout_tree);
-
- let child_sum: u32 =
- children.iter().map(|child| child.ratio.unwrap_or(1)).sum();
-
- let mut col_constraints = Vec::with_capacity(children.len());
- for child in children {
- col_constraints
- .push(Constraint::Ratio(child.ratio.unwrap_or(1), child_sum));
- let widget_id = layout_tree.new_node(LayoutNode::Widget);
- col_id.append(widget_id, &mut layout_tree);
-
- if let Some(true) = child.default {
+ let col_id = arena.new_node(LayoutNode::Col(ColLayout::new(
+ ratio
+ .map(|ratio| LayoutRule::Expand { ratio })
+ .unwrap_or(LayoutRule::Child),
+ )));
+ row_id.append(col_id, &mut arena);
+
+ for widget in col_child {
+ let widget_id =
+ arena.new_node(LayoutNode::Widget(WidgetLayout::default()));
+ col_id.append(widget_id, &mut arena);
+
+ if let Some(true) = widget.default {
first_selected = Some(widget_id);
}
@@ -1177,7 +1351,7 @@ pub fn create_layout_tree(
first_widget_seen = Some(widget_id);
}
- let widget_type = child.widget_type.parse::<BottomWidgetType>()?;
+ let widget_type = widget.widget_type.parse::<BottomWidgetType>()?;
used_widgets.add(&widget_type);
add_widget_to_map(
@@ -1186,25 +1360,17 @@ pub fn create_layout_tree(
widget_id,
&process_defaults,
app_config_fields,
+ LayoutRule::default(),
+ widget.rule.unwrap_or_default(),
)?;
}
- layout_tree[col_id]
- .get_mut()
- .set_constraints(col_constraints);
}
}
}
- layout_tree[row_id]
- .get_mut()
- .set_constraints(row_constraints);
}
}
- layout_tree[root_id]
- .get_mut()
- .set_constraints(root_constraints);
let selected: NodeId;
-
if let Some(first_selected) = first_selected {
selected = first_selected;
} else if let Some(first_widget_seen) = first_widget_seen {
@@ -1216,7 +1382,7 @@ pub fn create_layout_tree(
}
Ok(LayoutCreationOutput {
- layout_tree,
+ layout_tree: arena,
root: root_id,
widget_lookup_map,
selected,
@@ -1264,7 +1430,7 @@ pub fn move_widget_selection(
.and_then(|(parent_id, parent_node)| match parent_node.get() {
LayoutNode::Row(_) => Some((parent_id, current_id)),
LayoutNode::Col(_) => find_first_row(layout_tree, parent_id),
- LayoutNode::Widget => None,
+ LayoutNode::Widget(_) => None,
})
}
@@ -1285,21 +1451,23 @@ pub fn move_widget_selection(
.and_then(|(parent_id, parent_node)| match parent_node.get() {
LayoutNode::Row(_) => find_first_col(layout_tree, parent_id),
LayoutNode::Col(_) => Some((parent_id, current_id)),
- LayoutNode::Widget => None,
+ LayoutNode::Widget(_) => None,
})
}
- /// Descends to a leaf.
+ /// Descends to a leaf node.
fn descend_to_leaf(layout_tree: &Arena<LayoutNode>, current_id: NodeId) -> NodeId {
if let Some(current_node) = layout_tree.get(current_id) {
match current_node.get() {
LayoutNode::Row(RowLayout {
last_selected_index,
- constraints: _,
+ parent_rule: _,
+ bound: _,
})
| LayoutNode::Col(ColLayout {
last_selected_index,
- constraints: _,
+ parent_rule: _,
+ bound: _,
}) => {
if let Some(next_child) =
current_id.children(layout_tree).nth(*last_selected_index)
@@ -1309,8 +1477,9 @@ pub fn move_widget_selection(
current_id
}
}
- LayoutNode::Widget => {
+ LayoutNode::Widget(_) => {
// Halt!
+ // TODO: How does this handle carousel?
current_id
}
}
@@ -1470,3 +1639,317 @@ pub fn move_widget_selection(
}
}
}
+
+/// Generates the bounds for each node in the `arena, taking into account per-leaf desires,
+/// and finally storing the calculated bounds in the given `arena`.
+///
+/// Stored bounds are given in *relative* coordinates - they are relative to their parents.
+/// That is, you may have a child widget "start" at (0, 0), but its parent is actually at x = 5,s
+/// so the absolute coordinate of the child widget is actually (5, 0).
+///
+/// The algorithm is mostly based on the algorithm used by Flutter, adapted to work for
+/// our use case. For more information, check out both:
+///
+/// - [How the constraint system works in Flutter](https://flutter.dev/docs/development/ui/layout/constraints)
+/// - [How Flutter does sublinear layout](https://flutter.dev/docs/resources/inside-flutter#sublinear-layout)
+pub fn generate_layout(
+ root: NodeId, arena: &mut Arena<LayoutNode>, area: Rect,
+ lookup_map: &FxHashMap<NodeId, TmpBottomWidget>,
+) {
+ // TODO: [Layout] Add some caching/dirty mechanisms to reduce calls.
+
+ /// A [`Size`] is a set of widths and heights that a node in our layout wants to be.
+ #[derive(Default, Clone, Copy, Debug)]
+ struct Size {
+ width: u16,
+ height: u16,
+ }
+
+ /// A [`LayoutConstraint`] is just a set of maximal widths/heights.
+ #[derive(Clone, Copy, Debug)]
+ struct LayoutConstraints {
+ max_width: u16,
+ max_height: u16,
+ }
+
+ impl LayoutConstraints {
+ fn new(max_width: u16, max_height: u16) -> Self {
+ Self {
+ max_width,
+ max_height,
+ }
+ }
+
+ /// Shrinks the width of itself given another width.
+ fn shrink_width(&mut self, width: u16) {
+ self.max_width = self.max_width.saturating_sub(width);
+ }
+
+ /// Shrinks the height of itself given another height.
+ fn shrink_height(&mut self, height: u16) {
+ self.max_height = self.max_height.saturating_sub(height);
+ }
+
+ /// Returns a new [`LayoutConstraints`] with a new width given a ratio.
+ fn ratio_width(&self, numerator: u32, denominator: u32) -> Self {
+ Self {
+ max_width: (self.max_width as u32 * numerator / denominator) as u16,
+ max_height: self.max_height,
+ }
+ }<