summaryrefslogtreecommitdiffstats
path: root/pure/src/flex.rs
diff options
context:
space:
mode:
Diffstat (limited to 'pure/src/flex.rs')
-rw-r--r--pure/src/flex.rs232
1 files changed, 232 insertions, 0 deletions
diff --git a/pure/src/flex.rs b/pure/src/flex.rs
new file mode 100644
index 00000000..8d473f08
--- /dev/null
+++ b/pure/src/flex.rs
@@ -0,0 +1,232 @@
+//! Distribute elements using a flex-based layout.
+// This code is heavily inspired by the [`druid`] codebase.
+//
+// [`druid`]: https://github.com/xi-editor/druid
+//
+// Copyright 2018 The xi-editor Authors, Héctor Ramón
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+use crate::Element;
+
+use iced_native::layout::{Limits, Node};
+use iced_native::{Alignment, Padding, Point, Size};
+
+/// The main axis of a flex layout.
+#[derive(Debug)]
+pub enum Axis {
+ /// The horizontal axis
+ Horizontal,
+
+ /// The vertical axis
+ Vertical,
+}
+
+impl Axis {
+ fn main(&self, size: Size) -> f32 {
+ match self {
+ Axis::Horizontal => size.width,
+ Axis::Vertical => size.height,
+ }
+ }
+
+ fn cross(&self, size: Size) -> f32 {
+ match self {
+ Axis::Horizontal => size.height,
+ Axis::Vertical => size.width,
+ }
+ }
+
+ fn pack(&self, main: f32, cross: f32) -> (f32, f32) {
+ match self {
+ Axis::Horizontal => (main, cross),
+ Axis::Vertical => (cross, main),
+ }
+ }
+}
+
+/// Computes the flex layout with the given axis and limits, applying spacing,
+/// padding and alignment to the items as needed.
+///
+/// It returns a new layout [`Node`].
+pub fn resolve<Message, Renderer>(
+ axis: Axis,
+ renderer: &Renderer,
+ limits: &Limits,
+ padding: Padding,
+ spacing: f32,
+ align_items: Alignment,
+ items: &[Element<Message, Renderer>],
+) -> Node
+where
+ Renderer: iced_native::Renderer,
+{
+ let limits = limits.pad(padding);
+ let total_spacing = spacing * items.len().saturating_sub(1) as f32;
+ let max_cross = axis.cross(limits.max());
+
+ let mut fill_sum = 0;
+ let mut cross = axis.cross(limits.min()).max(axis.cross(limits.fill()));
+ let mut available = axis.main(limits.max()) - total_spacing;
+
+ let mut nodes: Vec<Node> = Vec::with_capacity(items.len());
+ nodes.resize(items.len(), Node::default());
+
+ if align_items == Alignment::Fill {
+ let mut fill_cross = axis.cross(limits.min());
+
+ items.iter().for_each(|child| {
+ let cross_fill_factor = match axis {
+ Axis::Horizontal => child.as_widget().height(),
+ Axis::Vertical => child.as_widget().width(),
+ }
+ .fill_factor();
+
+ if cross_fill_factor == 0 {
+ let (max_width, max_height) = axis.pack(available, max_cross);
+
+ let child_limits =
+ Limits::new(Size::ZERO, Size::new(max_width, max_height));
+
+ let layout = child.as_widget().layout(renderer, &child_limits);
+ let size = layout.size();
+
+ fill_cross = fill_cross.max(axis.cross(size));
+ }
+ });
+
+ cross = fill_cross;
+ }
+
+ for (i, child) in items.iter().enumerate() {
+ let fill_factor = match axis {
+ Axis::Horizontal => child.as_widget().width(),
+ Axis::Vertical => child.as_widget().height(),
+ }
+ .fill_factor();
+
+ if fill_factor == 0 {
+ let (min_width, min_height) = if align_items == Alignment::Fill {
+ axis.pack(0.0, cross)
+ } else {
+ axis.pack(0.0, 0.0)
+ };
+
+ let (max_width, max_height) = if align_items == Alignment::Fill {
+ axis.pack(available, cross)
+ } else {
+ axis.pack(available, max_cross)
+ };
+
+ let child_limits = Limits::new(
+ Size::new(min_width, min_height),
+ Size::new(max_width, max_height),
+ );
+
+ let layout = child.as_widget().layout(renderer, &child_limits);
+ let size = layout.size();
+
+ available -= axis.main(size);
+
+ if align_items != Alignment::Fill {
+ cross = cross.max(axis.cross(size));
+ }
+
+ nodes[i] = layout;
+ } else {
+ fill_sum += fill_factor;
+ }
+ }
+
+ let remaining = available.max(0.0);
+
+ for (i, child) in items.iter().enumerate() {
+ let fill_factor = match axis {
+ Axis::Horizontal => child.as_widget().width(),
+ Axis::Vertical => child.as_widget().height(),
+ }
+ .fill_factor();
+
+ if fill_factor != 0 {
+ let max_main = remaining * fill_factor as f32 / fill_sum as f32;
+ let min_main = if max_main.is_infinite() {
+ 0.0
+ } else {
+ max_main
+ };
+
+ let (min_width, min_height) = if align_items == Alignment::Fill {
+ axis.pack(min_main, cross)
+ } else {
+ axis.pack(min_main, axis.cross(limits.min()))
+ };
+
+ let (max_width, max_height) = if align_items == Alignment::Fill {
+ axis.pack(max_main, cross)
+ } else {
+ axis.pack(max_main, max_cross)
+ };
+
+ let child_limits = Limits::new(
+ Size::new(min_width, min_height),
+ Size::new(max_width, max_height),
+ );
+
+ let layout = child.as_widget().layout(renderer, &child_limits);
+
+ if align_items != Alignment::Fill {
+ cross = cross.max(axis.cross(layout.size()));
+ }
+
+ nodes[i] = layout;
+ }
+ }
+
+ let pad = axis.pack(padding.left as f32, padding.top as f32);
+ let mut main = pad.0;
+
+ for (i, node) in nodes.iter_mut().enumerate() {
+ if i > 0 {
+ main += spacing;
+ }
+
+ let (x, y) = axis.pack(main, pad.1);
+
+ node.move_to(Point::new(x, y));
+
+ match axis {
+ Axis::Horizontal => {
+ node.align(
+ Alignment::Start,
+ align_items,
+ Size::new(0.0, cross),
+ );
+ }
+ Axis::Vertical => {
+ node.align(
+ align_items,
+ Alignment::Start,
+ Size::new(cross, 0.0),
+ );
+ }
+ }
+
+ let size = node.size();
+
+ main += axis.main(size);
+ }
+
+ let (width, height) = axis.pack(main - pad.0, cross);
+ let size = limits.resolve(Size::new(width, height));
+
+ Node::with_children(size.pad(padding), nodes)
+}