#[cfg(test)]
mod compute_tests;
use crate::context::Context;
use crate::mouse::Mouse;
use rio_backend::config::layout::Margin;
use rio_backend::crosswords::grid::Dimensions;
use rio_backend::event::EventListener;
use rio_backend::sugarloaf::{layout::TextDimensions, Object, Rect, RichText, Sugarloaf};
use rustc_hash::FxHashMap;
use taffy::{
geometry, style_helpers::length, AvailableSpace, Display, NodeId, Style, TaffyError,
TaffyTree,
};
const MIN_COLS: usize = 2;
const MIN_LINES: usize = 1;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum BorderDirection {
Vertical,
Horizontal,
}
#[derive(Debug, Clone, Copy)]
pub struct PanelBorder {
pub direction: BorderDirection,
pub left_or_top: NodeId,
pub right_or_bottom: NodeId,
}
#[derive(Debug, Clone, Copy)]
pub struct ResizeState {
pub border: PanelBorder,
pub start_pos: f32,
pub original_sizes: (f32, f32),
}
fn compute(
width: f32,
height: f32,
dimensions: TextDimensions,
line_height: f32,
margin: Margin,
) -> (usize, usize) {
if width <= 0.0 || height <= 0.0 || dimensions.scale <= 0.0 || line_height <= 0.0 {
return (MIN_COLS, MIN_LINES);
}
let scale = dimensions.scale;
let available_width = width - (margin.left * scale) - (margin.right * scale);
let available_height = height - (margin.top * scale) - (margin.bottom * scale);
if available_width <= 0.0 || available_height <= 0.0 {
return (MIN_COLS, MIN_LINES);
}
let visible_columns =
std::cmp::max((available_width / dimensions.width) as usize, MIN_COLS);
let char_height = dimensions.height;
if char_height <= 0.0 {
return (visible_columns, MIN_LINES);
}
let lines = (available_height / char_height).floor();
let visible_lines = std::cmp::max(lines as usize, MIN_LINES);
(visible_columns, visible_lines)
}
#[inline]
fn create_border(color: [f32; 4], position: [f32; 2], size: [f32; 2]) -> Object {
Object::Rect(Rect::new(position[0], position[1], size[0], size[1], color))
}
#[derive(Debug, Clone, Copy)]
pub struct BorderConfig {
pub width: f32,
pub color: [f32; 4],
}
impl Default for BorderConfig {
fn default() -> Self {
Self {
width: 2.0,
color: [0.8, 0.8, 0.8, 1.0],
}
}
}
pub struct ContextGrid<T: EventListener> {
pub width: f32,
pub height: f32,
pub current: NodeId,
pub scaled_margin: Margin,
scale: f32,
inner: FxHashMap<NodeId, ContextGridItem<T>>,
pub root: Option<NodeId>,
panel_config: rio_backend::config::layout::Panel,
tree: TaffyTree<()>,
root_node: NodeId,
border_config: BorderConfig,
}
pub struct ContextGridItem<T: EventListener> {
pub val: Context<T>,
rich_text_object: Object,
pub layout_rect: [f32; 4],
}
impl<T: rio_backend::event::EventListener> ContextGridItem<T> {
pub fn new(context: Context<T>) -> Self {
let rich_text_object = Object::RichText(RichText {
id: context.rich_text_id,
lines: None,
render_data: rio_backend::sugarloaf::RichTextRenderData {
position: [0.0, 0.0],
should_repaint: false,
should_remove: false,
hidden: false,
},
});
Self {
val: context,
rich_text_object,
layout_rect: [0.0; 4],
}
}
#[inline]
pub fn context(&self) -> &Context<T> {
&self.val
}
#[inline]
pub fn context_mut(&mut self) -> &mut Context<T> {
&mut self.val
}
fn set_position(&mut self, position: [f32; 2]) {
if let Object::RichText(ref mut rich_text) = self.rich_text_object {
rich_text.render_data.position = position;
}
}
}
impl<T: rio_backend::event::EventListener> ContextGrid<T> {
pub fn new(
context: Context<T>,
scaled_margin: Margin,
border_color: [f32; 4],
_border_active_color: [f32; 4],
panel_config: rio_backend::config::layout::Panel,
) -> Self {
let width = context.dimension.width;
let height = context.dimension.height;
let scale = context.dimension.dimension.scale;
let mut tree: TaffyTree<()> = TaffyTree::new();
let available_width = width - scaled_margin.left - scaled_margin.right;
let available_height = height - scaled_margin.top - scaled_margin.bottom;
let root_style = Style {
display: Display::Flex,
gap: geometry::Size {
width: length(panel_config.column_gap * scale),
height: length(panel_config.row_gap * scale),
},
size: geometry::Size {
width: length(available_width),
height: length(available_height),
},
..Default::default()
};
let root_node = tree
.new_leaf(root_style)
.expect("Failed to create root node");
let panel_style = Style {
display: Display::Flex,
flex_grow: 1.0,
flex_shrink: 1.0,
padding: geometry::Rect {
left: length(panel_config.padding.left * scale),
right: length(panel_config.padding.right * scale),
top: length(panel_config.padding.top * scale),
bottom: length(panel_config.padding.bottom * scale),
},
margin: geometry::Rect {
left: length(panel_config.margin.left * scale),
right: length(panel_config.margin.right * scale),
top: length(panel_config.margin.top * scale),
bottom: length(panel_config.margin.bottom * scale),
},
..Default::default()
};
let panel_node = tree
.new_leaf(panel_style)
.expect("Failed to create panel node");
tree.add_child(root_node, panel_node)
.expect("Failed to add child");
let mut inner = FxHashMap::default();
inner.insert(panel_node, ContextGridItem::new(context));
let border_config = BorderConfig {
width: panel_config.border_width,
color: border_color,
};
let mut grid = Self {
inner,
current: panel_node,
scaled_margin,
scale,
width,
height,
root: Some(panel_node),
panel_config,
tree,
root_node,
border_config,
};
grid.calculate_positions();
grid
}
#[inline]
pub fn get_mut(&mut self, key: NodeId) -> Option<&mut ContextGridItem<T>> {
self.inner.get_mut(&key)
}
#[inline]
pub fn get_by_route_id(
&mut self,
route_id: usize,
) -> Option<&mut ContextGridItem<T>> {
self.inner
.values_mut()
.find(|item| item.val.route_id == route_id)
}
#[inline]
pub fn len(&self) -> usize {
self.inner.len()
}
pub fn panel_count(&self) -> usize {
self.inner.len()
}
pub fn should_draw_borders(&self) -> bool {
self.panel_count() > 1
}
fn try_update_size(&mut self, width: f32, height: f32) -> Result<(), TaffyError> {
let available_width = width - self.scaled_margin.left - self.scaled_margin.right;
let available_height =
height - self.scaled_margin.top - self.scaled_margin.bottom;
let mut style = self.tree.style(self.root_node)?.clone();
style.size = geometry::Size {
width: length(available_width),
height: length(available_height),
};
self.tree.set_style(self.root_node, style)?;
Ok(())
}
fn compute_layout(&mut self) -> Result<(), TaffyError> {
let available = geometry::Size {
width: AvailableSpace::MaxContent,
height: AvailableSpace::MaxContent,
};
self.tree.compute_layout(self.root_node, available)?;
self.update_layout_rects();
Ok(())
}
fn update_layout_rects(&mut self) {
let mut stack: Vec<(NodeId, f32, f32)> = vec![(self.root_node, 0.0, 0.0)];
while let Some((node, parent_x, parent_y)) = stack.pop() {
let layout = match self.tree.layout(node) {
Ok(l) => l,
Err(_) => continue,
};
let abs_x = parent_x + layout.location.x;
let abs_y = parent_y + layout.location.y;
if let Some(item) = self.inner.get_mut(&node) {
item.layout_rect = [abs_x, abs_y, layout.size.width, layout.size.height];
}
if let Ok(children) = self.tree.children(node) {
for child in children {
stack.push((child, abs_x, abs_y));
}
}
}
}
pub fn find_context_at_position(&self, x: f32, y: f32) -> Option<NodeId> {
let adj_x = x - self.scaled_margin.left;
let adj_y = y - self.scaled_margin.top;
for (&node_id, item) in &self.inner {
let [left, top, width, height] = item.layout_rect;
if adj_x >= left
&& adj_x < left + width
&& adj_y >= top
&& adj_y < top + height
{
return Some(node_id);
}
}
None
}
pub fn find_border_at_position(&self, x: f32, y: f32) -> Option<PanelBorder> {
if self.inner.len() <= 1 {
return None;
}
let adj_x = x - self.scaled_margin.left;
let adj_y = y - self.scaled_margin.top;
let hit_half = (self.border_config.width / 2.0 + 3.0) * self.scale;
self.walk_separators(|dir, center, span, child_a, child_b| {
let hit = match dir {
BorderDirection::Vertical => {
(adj_x - center).abs() < hit_half
&& adj_y >= span[0]
&& adj_y <= span[1]
}
BorderDirection::Horizontal => {
(adj_y - center).abs() < hit_half
&& adj_x >= span[0]
&& adj_x <= span[1]
}
};
if hit {
Some(PanelBorder {
direction: dir,
left_or_top: child_a,
right_or_bottom: child_b,
})
} else {
None
}
})
}
pub fn get_panel_size(&self, node: NodeId, direction: BorderDirection) -> f32 {
if let Ok(layout) = self.tree.layout(node) {
match direction {
BorderDirection::Vertical => layout.size.width,
BorderDirection::Horizontal => layout.size.height,
}
} else {
0.0
}
}
pub fn resize_border(
&mut self,
border: &PanelBorder,
original_sizes: (f32, f32),
delta: f32,
sugarloaf: &mut Sugarloaf,
) {
let min_size = 50.0 * self.scale;
let new_a = (original_sizes.0 + delta).max(min_size);
let new_b = (original_sizes.1 - delta).max(min_size);
match border.direction {
BorderDirection::Vertical => {
let _ = self.set_panel_size(border.left_or_top, Some(new_a), None);
let _ = self.set_panel_size(border.right_or_bottom, Some(new_b), None);
}
BorderDirection::Horizontal => {
let _ = self.set_panel_size(border.left_or_top, None, Some(new_a));
let _ = self.set_panel_size(border.right_or_bottom, None, Some(new_b));
}
}
self.apply_taffy_layout(sugarloaf);
}
pub fn get_panel_borders(&self) -> Vec<rio_backend::sugarloaf::Object> {
if !self.should_draw_borders() {
return vec![];
}
let mut separators = Vec::new();
let border_width = self.border_config.width;
let color = self.border_config.color;
self.walk_separators(|dir, center, span, _child_a, _child_b| -> Option<()> {
match dir {
BorderDirection::Vertical => {
separators.push(create_border(
color,
[center - border_width / 2.0, span[0]],
[border_width, span[1] - span[0]],
));
}
BorderDirection::Horizontal => {
separators.push(create_border(
color,
[span[0], center - border_width / 2.0],
[span[1] - span[0], border_width],
));
}
}
None });
separators
}
fn walk_separators<R>(
&self,
mut visitor: impl FnMut(BorderDirection, f32, [f32; 2], NodeId, NodeId) -> Option<R>,
) -> Option<R> {
let mut stack: Vec<(NodeId, f32, f32)> = vec![(self.root_node, 0.0, 0.0)];
while let Some((node, parent_x, parent_y)) = stack.pop() {
let children = match self.tree.children(node) {
Ok(c) => c,
_ => continue,
};
let node_layout = match self.tree.layout(node) {
Ok(l) => l,
Err(_) => continue,
};
let abs_x = parent_x + node_layout.location.x;
let abs_y = parent_y + node_layout.location.y;
for &child in &children {
stack.push((child, abs_x, abs_y));
}
if children.len() < 2 {
continue;
}
let is_row = match self.tree.style(node) {
Ok(s) => matches!(
s.flex_direction,
taffy::FlexDirection::Row | taffy::FlexDirection::RowReverse
),
Err(_) => continue,
};
for i in 0..children.len() - 1 {
let la = match self.tree.layout(children[i]) {
Ok(l) => l,
Err(_) => continue,
};
let lb = match self.tree.layout(children[i + 1]) {
Ok(l) => l,
Err(_) => continue,
};
if is_row {
let (left, right, left_id, right_id) =
if la.location.x < lb.location.x {
(la, lb, children[i], children[i + 1])
} else {
(lb, la, children[i + 1], children[i])
};
let left_edge = abs_x + left.location.x + left.size.width;
let right_start = abs_x + right.location.x;
let center = (left_edge + right_start) / 2.0;
let min_y = abs_y + left.location.y.min(right.location.y);
let max_y = abs_y
+ (left.location.y + left.size.height)
.max(right.location.y + right.size.height);
if let Some(r) = visitor(
BorderDirection::Vertical,
center,
[min_y, max_y],
left_id,
right_id,
) {
return Some(r);
}
} else {
let (top, bottom, top_id, bottom_id) =
if la.location.y < lb.location.y {
(la, lb, children[i], children[i + 1])
} else {
(lb, la, children[i + 1], children[i])
};
let top_edge = abs_y + top.location.y + top.size.height;
let bottom_start = abs_y + bottom.location.y;
let center = (top_edge + bottom_start) / 2.0;
let min_x = abs_x + top.location.x.min(bottom.location.x);
let max_x = abs_x
+ (top.location.x + top.size.width)
.max(bottom.location.x + bottom.size.width);
if let Some(r) = visitor(
BorderDirection::Horizontal,
center,
[min_x, max_x],
top_id,
bottom_id,
) {
return Some(r);
}
}
}
}
None
}
#[inline]
pub fn get_scaled_margin(&self) -> Margin {
self.scaled_margin
}
fn create_panel_style(&self) -> Style {
let scale = self.scale;
Style {
display: Display::Flex,
flex_grow: 1.0,
flex_shrink: 1.0,
padding: geometry::Rect {
left: length(self.panel_config.padding.left * scale),
right: length(self.panel_config.padding.right * scale),
top: length(self.panel_config.padding.top * scale),
bottom: length(self.panel_config.padding.bottom * scale),
},
margin: geometry::Rect {
left: length(self.panel_config.margin.left * scale),
right: length(self.panel_config.margin.right * scale),
top: length(self.panel_config.margin.top * scale),
bottom: length(self.panel_config.margin.bottom * scale),
},
..Default::default()
}
}
fn try_split_right(&mut self) -> Result<NodeId, TaffyError> {
self.split_panel(taffy::FlexDirection::Row)
}
fn try_split_down(&mut self) -> Result<NodeId, TaffyError> {
self.split_panel(taffy::FlexDirection::Column)
}
fn split_panel(
&mut self,
direction: taffy::FlexDirection,
) -> Result<NodeId, TaffyError> {
let current_node = self.current;
if !self.inner.contains_key(¤t_node) {
return Err(TaffyError::InvalidInputNode(self.root_node));
}
let parent_node = self.tree.parent(current_node).unwrap_or(self.root_node);
let current_style = self.tree.style(current_node)?.clone();
let scale = self.scale;
let container_style = Style {
display: Display::Flex,
flex_direction: direction,
flex_basis: current_style.flex_basis,
flex_grow: current_style.flex_grow,
flex_shrink: current_style.flex_shrink,
gap: geometry::Size {
width: length(self.panel_config.column_gap * scale),
height: length(self.panel_config.row_gap * scale),
},
..Default::default()
};
let container_node = self.tree.new_leaf(container_style)?;
let mut reset_style = current_style;
reset_style.flex_basis = taffy::Dimension::auto();
reset_style.flex_grow = 1.0;
reset_style.flex_shrink = 1.0;
self.tree.set_style(current_node, reset_style)?;
let new_node = self.tree.new_leaf(self.create_panel_style())?;
let children = self.tree.children(parent_node)?;
let current_index = children.iter().position(|&n| n == current_node);
self.tree.remove_child(parent_node, current_node)?;
self.tree.add_child(container_node, current_node)?;
self.tree.add_child(container_node, new_node)?;
if let Some(idx) = current_index {
self.tree
.insert_child_at_index(parent_node, idx, container_node)?;
} else {
self.tree.add_child(parent_node, container_node)?;
}
Ok(new_node)
}
fn set_panel_size(
&mut self,
node: NodeId,
width: Option<f32>,
height: Option<f32>,
) -> Result<(), TaffyError> {
let mut style = self.tree.style(node)?.clone();
if let Some(w) = width {
style.flex_basis = length(0.0);
style.flex_grow = w;
style.flex_shrink = 1.0;
} else if let Some(h) = height {
style.flex_basis = length(0.0);
style.flex_grow = h;
style.flex_shrink = 1.0;
}
self.tree.set_style(node, style)?;
Ok(())
}
fn reset_panel_styles_to_flexible(&mut self) {
let mut stack = vec![self.root_node];
while let Some(node) = stack.pop() {
if let Ok(mut style) = self.tree.style(node).cloned() {
style.flex_basis = taffy::Dimension::auto();
style.flex_grow = 1.0;
style.flex_shrink = 1.0;
let _ = self.tree.set_style(node, style);
}
if let Ok(children) = self.tree.children(node) {
for child in children {
stack.push(child);
}
}
}
}
fn collapse_single_child_containers(&mut self) {
loop {
let mut collapsed = false;
let mut stack = vec![self.root_node];
while let Some(node) = stack.pop() {
let children = match self.tree.children(node) {
Ok(c) => c,
_ => continue,
};
for &child in &children {
if self.inner.contains_key(&child) {
continue;
}
let grandchildren = match self.tree.children(child) {
Ok(gc) => gc,
_ => continue,
};
if grandchildren.len() == 1 {
let grandchild = grandchildren[0];
let child_idx = children.iter().position(|&c| c == child);
if let Some(idx) = child_idx {
if let Ok(container_style) = self.tree.style(child).cloned() {
if let Ok(mut gc_style) =
self.tree.style(grandchild).cloned()
{
gc_style.flex_basis = container_style.flex_basis;
gc_style.flex_grow = container_style.flex_grow;
gc_style.flex_shrink = container_style.flex_shrink;
let _ = self.tree.set_style(grandchild, gc_style);
}
}
let _ = self.tree.remove_child(child, grandchild);
let _ = self.tree.remove_child(node, child);
let _ =
self.tree.insert_child_at_index(node, idx, grandchild);
collapsed = true;
break; }
} else if grandchildren.is_empty() {
let _ = self.tree.remove_child(node, child);
collapsed = true;
break;
} else {
stack.push(child);
}
}
if collapsed {
break;
}
}
if !collapsed {
break;
}
}
}
fn find_horizontal_neighbors(&self, node_id: NodeId) -> Option<(NodeId, NodeId)> {
if !self.inner.contains_key(&node_id) {
return None;
}
let current_layout = self.tree.layout(node_id).ok()?;
let gap = self.panel_config.column_gap * self.scale;
for &other_id in self.inner.keys() {
if other_id == node_id {
continue;
}
let other_layout = self.tree.layout(other_id).ok()?;
let current_y_end = current_layout.location.y + current_layout.size.height;
let other_y_end = other_layout.location.y + other_layout.size.height;
let y_overlap = current_layout.location.y < other_y_end
&& other_layout.location.y < current_y_end;
if y_overlap {
let other_right = other_layout.location.x + other_layout.size.width;
let distance = current_layout.location.x - other_right;
if distance >= 0.0 && distance <= gap + 1.0 {
return Some((other_id, node_id));
}
}
}
let current_right = current_layout.location.x + current_layout.size.width;
for &other_id in self.inner.keys() {
if other_id == node_id {
continue;
}
let other_layout = self.tree.layout(other_id).ok()?;
let current_y_end = current_layout.location.y + current_layout.size.height;
let other_y_end = other_layout.location.y + other_layout.size.height;
let y_overlap = current_layout.location.y < other_y_end
&& other_layout.location.y < current_y_end;
if y_overlap {
let distance = other_layout.location.x - current_right;
if distance >= 0.0 && distance <= gap + 1.0 {
return Some((node_id, other_id));
}
}
}
None
}
fn find_vertical_neighbors(&self, node_id: NodeId) -> Option<(NodeId, NodeId)> {
if !self.inner.contains_key(&node_id) {
return None;
}
let current_layout = self.tree.layout(node_id).ok()?;
let gap = self.panel_config.row_gap * self.scale;
for &other_id in self.inner.keys() {
if other_id == node_id {
continue;
}
let other_layout = self.tree.layout(other_id).ok()?;
let current_x_end = current_layout.location.x + current_layout.size.width;
let other_x_end = other_layout.location.x + other_layout.size.width;
let x_overlap = current_layout.location.x < other_x_end
&& other_layout.location.x < current_x_end;
if x_overlap {
let other_bottom = other_layout.location.y + other_layout.size.height;
let distance = current_layout.location.y - other_bottom;
if distance >= 0.0 && distance <= gap + 1.0 {
return Some((other_id, node_id));
}
}
}
let current_bottom = current_layout.location.y + current_layout.size.height;
for &other_id in self.inner.keys() {
if other_id == node_id {
continue;
}
let other_layout = self.tree.layout(other_id).ok()?;
let current_x_end = current_layout.location.x + current_layout.size.width;
let other_x_end = other_layout.location.x + other_layout.size.width;
let x_overlap = current_layout.location.x < other_x_end
&& other_layout.location.x < current_x_end;
if x_overlap {
let distance = other_layout.location.y - current_bottom;
if distance >= 0.0 && distance <= gap + 1.0 {
return Some((node_id, other_id));
}
}
}
None
}
fn apply_taffy_layout(&mut self, sugarloaf: &mut Sugarloaf) -> bool {
if self.compute_layout().is_err() {
return false;
}
let scale = sugarloaf.ctx.scale();
let is_multi_panel = self.inner.len() > 1;
for item in self.inner.values_mut() {
let [abs_x, abs_y, width, height] = item.layout_rect;
let x = (abs_x + self.scaled_margin.left) / scale;
let y = (abs_y + self.scaled_margin.top) / scale;
item.val.dimension.margin = Margin::all(0.0);
item.val.dimension.update_width(width);
item.val.dimension.update_height(height);
let mut terminal = item.val.terminal.lock();
terminal.resize::<ContextDimension>(item.val.dimension);
drop(terminal);
let winsize =
crate::renderer::utils::terminal_dimensions(&item.val.dimension);
let _ = item.val.messenger.send_resize(winsize);
sugarloaf.set_position(item.val.rich_text_id, x, y);
if is_multi_panel {
let bounds_x = abs_x + self.scaled_margin.left;
let bounds_y = abs_y + self.scaled_margin.top;
sugarloaf.set_bounds(
item.val.rich_text_id,
Some([bounds_x, bounds_y, width, height]),
);
} else {
sugarloaf.set_bounds(item.val.rich_text_id, None);
}
}
true
}
#[inline]
pub fn contexts_mut(&mut self) -> &mut FxHashMap<NodeId, ContextGridItem<T>> {
&mut self.inner
}
pub fn get_ordered_keys(&self) -> Vec<NodeId> {
let mut panels: Vec<(NodeId, f32, f32)> = self
.inner
.iter()
.map(|(&id, item)| (id, item.layout_rect[1], item.layout_rect[0])) .collect();
panels.sort_by(|a, b| {
a.1.partial_cmp(&b.1)
.unwrap_or(std::cmp::Ordering::Equal)
.then(a.2.partial_cmp(&b.2).unwrap_or(std::cmp::Ordering::Equal))
});
panels.into_iter().map(|(id, _, _)| id).collect()
}
#[inline]
pub fn select_next_split(&mut self) {
if self.inner.len() == 1 {
return;
}
let keys = self.get_ordered_keys();
if let Some(current_pos) = keys.iter().position(|&k| k == self.current) {
if current_pos >= keys.len() - 1 {
self.current = keys[0];
} else {
self.current = keys[current_pos + 1];
}
}
}
#[inline]
pub fn select_next_split_no_loop(&mut self) -> bool {
if self.inner.len() == 1 {
return false;
}
let keys = self.get_ordered_keys();
if let Some(current_pos) = keys.iter().position(|&k| k == self.current) {
if current_pos >= keys.len() - 1 {
return false;
} else {
self.current = keys[current_pos + 1];
return true;
}
}
false
}
#[inline]
pub fn select_prev_split(&mut self) {
if self.inner.len() == 1 {
return;
}
let keys = self.get_ordered_keys();
if let Some(current_pos) = keys.iter().position(|&k| k == self.current) {
if current_pos == 0 {
self.current = keys[keys.len() - 1];
} else {
self.current = keys[current_pos - 1];
}
}
}
#[inline]
pub fn select_prev_split_no_loop(&mut self) -> bool {
if self.inner.len() == 1 {
return false;
}
let keys = self.get_ordered_keys();
if let Some(current_pos) = keys.iter().position(|&k| k == self.current) {
if current_pos == 0 {
return false;
} else {
self.current = keys[current_pos - 1];
return true;
}
}
false
}
#[inline]
pub fn current_item(&self) -> Option<&ContextGridItem<T>> {
self.inner.get(&self.current)
}
pub fn current(&self) -> &Context<T> {
if let Some(item) = self.inner.get(&self.current) {
&item.val
} else {
tracing::error!("Current key {:?} not found in grid", self.current);
if let Some(root) = self.root {
if let Some(item) = self.inner.get(&root) {
return &item.val;
}
}
panic!("Grid is in an invalid state - no contexts available");
}
}
#[inline]
pub fn current_mut(&mut self) -> &mut Context<T> {
let current_key = self.current;
if !self.inner.contains_key(¤t_key) {
tracing::error!("Current key {:?} not found in grid", current_key);
if let Some(root) = self.root {
self.current = root;
} else if let Some(first_key) = self.inner.keys().next() {
self.current = *first_key;
self.root = Some(*first_key);
} else {
panic!("Grid is in an invalid state - no contexts available");
}
}
let current_key = self.current;
if let Some(item) = self.inner.get_mut(¤t_key) {
&mut item.val
} else {
panic!(
"Grid is in an invalid state - current key not found after fix attempt"
);
}
}
pub fn current_context_with_computed_dimension(&self) -> (&Context<T>, Margin) {
let len = self.inner.len();
if len <= 1 {
if let Some(item) = self.inner.get(&self.current) {
return (&item.val, self.scaled_margin);
} else if let Some(root) = self.root {
if let Some(item) = self.inner.get(&root) {
return (&item.val, self.scaled_margin);
}
}
panic!("Grid is in an invalid state - no contexts available");
}
if let Some(current_item) = self.inner.get(&self.current) {
let [abs_x, abs_y, _, _] = current_item.layout_rect;
let margin = Margin {
left: self.scaled_margin.left + abs_x,
top: self.scaled_margin.top + abs_y,
right: self.scaled_margin.right,
bottom: self.scaled_margin.bottom,
};
(¤t_item.val, margin)
} else {
tracing::error!("Current key {:?} not found in grid", self.current);
if let Some(root) = self.root {
if let Some(item) = self.inner.get(&root) {
return (&item.val, self.scaled_margin);
}
}
panic!("Grid is in an invalid state - no contexts available");
}
}
#[inline]
pub fn select_current_based_on_mouse(&mut self, mouse: &Mouse) -> bool {
if self.inner.len() <= 1 {
return false;
}
let x = mouse.x as f32;
let y = mouse.y as f32;
if let Some(context_id) = self.find_context_at_position(x, y) {
self.current = context_id;
return true;
}
false
}
#[inline]
pub fn grid_dimension(&self) -> ContextDimension {
if let Some(current_item) = self.inner.get(&self.current) {
let current_context_dimension = current_item.val.dimension;
let scale = current_context_dimension.dimension.scale;
let unscaled_margin = if scale > 0.0 {
Margin::new(
self.scaled_margin.top / scale,
self.scaled_margin.right / scale,
self.scaled_margin.bottom / scale,
self.scaled_margin.left / scale,
)
} else {
self.scaled_margin
};
ContextDimension::build(
self.width,
self.height,
current_context_dimension.dimension,
current_context_dimension.line_height,
unscaled_margin,
)
} else {
tracing::error!("Current key {:?} not found in grid", self.current);
ContextDimension::default()
}
}
pub fn update_scaled_margin(&mut self, scaled_margin: Margin) {
self.scaled_margin = scaled_margin;
}
pub fn update_line_height(&mut self, line_height: f32) {
for context in self.inner.values_mut() {
context.val.dimension.update_line_height(line_height);
}
}
pub fn update_dimensions(&mut self, sugarloaf: &mut Sugarloaf) {
for context in self.inner.values_mut() {
if let Some(layout) = sugarloaf.get_text_layout(&context.val.rich_text_id) {
context.val.dimension.update_dimensions(layout.dimensions);
}
}
self.apply_taffy_layout(sugarloaf);
}
pub fn resize(&mut self, new_width: f32, new_height: f32, sugarloaf: &mut Sugarloaf) {
self.width = new_width;
self.height = new_height;
let _ = self.try_update_size(new_width, new_height);
self.apply_taffy_layout(sugarloaf);
}
#[inline]
pub fn calculate_positions(&mut self) {
if self.inner.is_empty() {
return;
}
if self.compute_layout().is_err() {
return;
}
for item in self.inner.values_mut() {
let x = item.layout_rect[0] + self.scaled_margin.left;
let y = item.layout_rect[1] + self.scaled_margin.top;
item.set_position([x, y]);
}
}
pub fn remove_current(&mut self, sugarloaf: &mut Sugarloaf) {
if self.inner.is_empty() {
tracing::error!("Attempted to remove from empty grid");
return;
}
if self.inner.len() == 1 {
tracing::warn!("Cannot remove the last remaining context");
return;
}
let to_remove = self.current;
if !self.inner.contains_key(&to_remove) {
tracing::error!("Current key {:?} not found in grid", to_remove);
return;
}
let rich_text_id = self.inner.get(&to_remove).map(|item| item.val.rich_text_id);
let ordered_keys = self.get_ordered_keys();
let current_pos = ordered_keys.iter().position(|&k| k == to_remove);
let next_current = if let Some(pos) = current_pos {
if pos + 1 < ordered_keys.len() {
ordered_keys[pos + 1]
} else if pos > 0 {
ordered_keys[pos - 1]
} else {
*ordered_keys
.iter()
.find(|&&k| k != to_remove)
.unwrap_or(&to_remove)
}
} else {
*self
.inner
.keys()
.find(|&&k| k != to_remove)
.unwrap_or(&to_remove)
};
let _ = self.tree.remove(to_remove);
self.inner.remove(&to_remove);
if let Some(id) = rich_text_id {
sugarloaf.remove_content(id);
}
if Some(to_remove) == self.root {
self.root = self.inner.keys().next().copied();
}
self.current = next_current;
self.collapse_single_child_containers();
if self.panel_count() > 0 {
if self.panel_count() == 1 {
self.reset_panel_styles_to_flexible();
}
self.apply_taffy_layout(sugarloaf);
}
}
pub fn split_right(&mut self, context: Context<T>, sugarloaf: &mut Sugarloaf) {
if !self.inner.contains_key(&self.current) {
return;
}
if let Ok(new_node) = self.try_split_right() {
let new_context = ContextGridItem::new(context);
self.inner.insert(new_node, new_context);
self.apply_taffy_layout(sugarloaf);
self.current = new_node;
}
}
pub fn split_down(&mut self, context: Context<T>, sugarloaf: &mut Sugarloaf) {
if !self.inner.contains_key(&self.current) {
return;
}
if let Ok(new_node) = self.try_split_down() {
let new_context = ContextGridItem::new(context);
self.inner.insert(new_node, new_context);
self.apply_taffy_layout(sugarloaf);
self.current = new_node;
}
}
pub fn move_divider_up(&mut self, amount: f32, sugarloaf: &mut Sugarloaf) -> bool {
if self.panel_count() <= 1 {
return false;
}
let current_node = self.current;
if let Some((top_node, bottom_node)) = self.find_vertical_neighbors(current_node)
{
let top_layout = match self.tree.layout(top_node).ok() {
Some(layout) => layout,
None => return false,
};
let bottom_layout = match self.tree.layout(bottom_node).ok() {
Some(layout) => layout,
None => return false,
};
let min_height = 50.0;
let new_top_height;
let new_bottom_height;
if current_node == bottom_node {
new_bottom_height = bottom_layout.size.height - amount;
new_top_height = top_layout.size.height + amount;
} else {
new_top_height = top_layout.size.height - amount;
new_bottom_height = bottom_layout.size.height + amount;
}
if new_top_height < min_height || new_bottom_height < min_height {
return false;
}
let _ = self.set_panel_size(top_node, None, Some(new_top_height));
let _ = self.set_panel_size(bottom_node, None, Some(new_bottom_height));
return self.apply_taffy_layout(sugarloaf);
}
false
}
pub fn move_divider_down(&mut self, amount: f32, sugarloaf: &mut Sugarloaf) -> bool {
if self.panel_count() <= 1 {
return false;
}
let current_node = self.current;
if let Some((top_node, bottom_node)) = self.find_vertical_neighbors(current_node)
{
let top_layout = match self.tree.layout(top_node).ok() {
Some(layout) => layout,
None => return false,
};
let bottom_layout = match self.tree.layout(bottom_node).ok() {
Some(layout) => layout,
None => return false,
};
let min_height = 50.0;
let new_top_height;
let new_bottom_height;
if current_node == bottom_node {
new_bottom_height = bottom_layout.size.height + amount;
new_top_height = top_layout.size.height - amount;
} else {
new_top_height = top_layout.size.height + amount;
new_bottom_height = bottom_layout.size.height - amount;
}
if new_top_height < min_height || new_bottom_height < min_height {
return false;
}
let _ = self.set_panel_size(top_node, None, Some(new_top_height));
let _ = self.set_panel_size(bottom_node, None, Some(new_bottom_height));
return self.apply_taffy_layout(sugarloaf);
}
false
}
pub fn move_divider_left(&mut self, amount: f32, sugarloaf: &mut Sugarloaf) -> bool {
if self.panel_count() <= 1 {
return false;
}
let current_node = self.current;
if let Some((left_node, right_node)) =
self.find_horizontal_neighbors(current_node)
{
let left_layout = match self.tree.layout(left_node).ok() {
Some(layout) => layout,
None => return false,
};
let right_layout = match self.tree.layout(right_node).ok() {
Some(layout) => layout,
None => return false,
};
let min_width = 100.0;
let new_left_width;
let new_right_width;
if current_node == right_node {
new_right_width = right_layout.size.width - amount;
new_left_width = left_layout.size.width + amount;
} else {
new_left_width = left_layout.size.width - amount;
new_right_width = right_layout.size.width + amount;
}
if new_left_width < min_width || new_right_width < min_width {
return false;
}
let _ = self.set_panel_size(left_node, Some(new_left_width), None);
let _ = self.set_panel_size(right_node, Some(new_right_width), None);
return self.apply_taffy_layout(sugarloaf);
}
false
}
pub fn move_divider_right(&mut self, amount: f32, sugarloaf: &mut Sugarloaf) -> bool {
if self.panel_count() <= 1 {
return false;
}
let current_node = self.current;
if let Some((left_node, right_node)) =
self.find_horizontal_neighbors(current_node)
{
let left_layout = match self.tree.layout(left_node).ok() {
Some(layout) => layout,
None => return false,
};
let right_layout = match self.tree.layout(right_node).ok() {
Some(layout) => layout,
None => return false,
};
let min_width = 100.0;
let new_left_width;
let new_right_width;
if current_node == right_node {
new_right_width = right_layout.size.width + amount;
new_left_width = left_layout.size.width - amount;
} else {
new_left_width = left_layout.size.width + amount;
new_right_width = right_layout.size.width - amount;
}
if new_left_width < min_width || new_right_width < min_width {
return false;
}
let _ = self.set_panel_size(left_node, Some(new_left_width), None);
let _ = self.set_panel_size(right_node, Some(new_right_width), None);
return self.apply_taffy_layout(sugarloaf);
}
false
}
#[inline]
pub fn set_all_rich_text_visibility(&self, sugarloaf: &mut Sugarloaf, hidden: bool) {
for item in self.inner.values() {
sugarloaf.set_visibility(item.val.rich_text_id, hidden);
}
}
#[inline]
pub fn remove_all_rich_text(&self, sugarloaf: &mut Sugarloaf) {
for item in self.inner.values() {
sugarloaf.remove_content(item.val.rich_text_id);
}
}
}
#[derive(Copy, Clone, Debug)]
pub struct ContextDimension {
pub width: f32,
pub height: f32,
pub columns: usize,
pub lines: usize,
pub dimension: TextDimensions,
pub margin: Margin,
pub line_height: f32,
}
impl Default for ContextDimension {
fn default() -> ContextDimension {
ContextDimension {
width: 0.,
height: 0.,
columns: MIN_COLS,
lines: MIN_LINES,
line_height: 1.,
dimension: TextDimensions::default(),
margin: Margin::default(),
}
}
}
impl ContextDimension {
pub fn build(
width: f32,
height: f32,
dimension: TextDimensions,
line_height: f32,
margin: Margin,
) -> Self {
let (columns, lines) = compute(width, height, dimension, line_height, margin);
Self {
width,
height,
columns,
lines,
dimension,
margin,
line_height,
}
}
#[inline]
pub fn update_width(&mut self, width: f32) {
self.width = width;
self.update();
}
#[inline]
pub fn update_height(&mut self, height: f32) {
self.height = height;
self.update();
}
#[inline]
pub fn update_line_height(&mut self, line_height: f32) {
self.line_height = line_height;
self.update();
}
#[inline]
pub fn update_dimensions(&mut self, dimensions: TextDimensions) {
self.dimension = dimensions;
self.update();
}
#[inline]
fn update(&mut self) {
let (columns, lines) = compute(
self.width,
self.height,
self.dimension,
self.line_height,
self.margin,
);
self.columns = columns;
self.lines = lines;
}
}
impl Dimensions for ContextDimension {
#[inline]
fn columns(&self) -> usize {
self.columns
}
#[inline]
fn screen_lines(&self) -> usize {
self.lines
}
#[inline]
fn total_lines(&self) -> usize {
self.screen_lines()
}
fn square_width(&self) -> f32 {
self.dimension.width
}
fn square_height(&self) -> f32 {
self.dimension.height
}
}