use std::cell::RefCell;
use std::rc::Rc;
use std::sync::Arc;
use crate::core::element::{Element, ElementKind};
use crate::layout::measure::min_size_constrained;
use crate::style::Span;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) enum SplitPaneSide {
Left,
Right,
}
#[derive(Default)]
pub(crate) struct SplitWrapSyncState {
left_pane_width: Option<u16>,
right_pane_width: Option<u16>,
left_scrollbar_cols: u16,
right_scrollbar_cols: u16,
layout_pass: u8,
pass1_left_heights: Option<Arc<Vec<u16>>>,
pass1_right_heights: Option<Arc<Vec<u16>>>,
}
pub(crate) type SharedSplitWrapSync = Rc<RefCell<SplitWrapSyncState>>;
pub(crate) fn new_split_wrap_sync_state() -> SharedSplitWrapSync {
Rc::new(RefCell::new(SplitWrapSyncState::default()))
}
pub(crate) struct SplitWrapDualPass {
sync: SharedSplitWrapSync,
}
impl SplitWrapDualPass {
pub(crate) const PASSES: [u8; 2] = [1, 2];
pub(crate) fn begin_measure<'a>(
children: impl IntoIterator<Item = (&'a Element, u16)>,
max_cross: Option<u16>,
) -> Option<Self> {
let children = children.into_iter().collect::<Vec<_>>();
let sync = shared_split_wrap_sync_for_dual_pass(children.iter().map(|(child, _)| *child))?;
for (child, width) in &children {
update_split_wrap_width_hint(child, *width);
}
reset_split_wrap_layout_after_hstack(&sync);
set_split_wrap_layout_pass(&sync, 1);
for (child, width) in &children {
let _ = min_size_constrained(child, Some(*width), max_cross);
}
set_split_wrap_layout_pass(&sync, 2);
Some(Self { sync })
}
pub(crate) fn begin_reconcile<'a>(
children: impl IntoIterator<Item = (&'a Element, u16)>,
) -> Option<Self> {
let children = children.into_iter().collect::<Vec<_>>();
let sync = shared_split_wrap_sync_for_dual_pass(children.iter().map(|(child, _)| *child))?;
for (child, width) in &children {
update_split_wrap_width_hint(child, *width);
}
reset_split_wrap_layout_after_hstack(&sync);
Some(Self { sync })
}
pub(crate) fn set_pass(&self, pass: u8) {
set_split_wrap_layout_pass(&self.sync, pass);
}
}
impl Drop for SplitWrapDualPass {
fn drop(&mut self) {
reset_split_wrap_layout_after_hstack(&self.sync);
}
}
pub(crate) fn update_split_wrap_pane_width(
state: &SharedSplitWrapSync,
side: SplitPaneSide,
pane_width: u16,
) {
let mut state = state.borrow_mut();
match side {
SplitPaneSide::Left => state.left_pane_width = Some(pane_width),
SplitPaneSide::Right => state.right_pane_width = Some(pane_width),
}
}
pub(crate) fn update_split_wrap_width_hint(el: &Element, available_width: u16) {
match &el.kind {
ElementKind::DocumentView(doc) => {
if let (Some(sync), Some(side)) = (&doc.split_wrap_sync, doc.split_wrap_side) {
update_split_wrap_pane_width(sync, side, available_width);
}
}
ElementKind::TextArea(area) => {
if let (Some(sync), Some(side)) = (&area.split_wrap_sync, area.split_wrap_side) {
update_split_wrap_pane_width(sync, side, available_width);
}
}
ElementKind::Frame(frame) => {
let inner_width = crate::widgets::frame::box_metrics::frame_inner_max_size(
frame,
Some(available_width),
None,
)
.0
.unwrap_or(available_width);
if let Some(child) = frame.child.as_deref() {
update_split_wrap_width_hint(child, inner_width);
}
}
ElementKind::Group(group) => {
update_split_wrap_width_hint(group.child.as_ref(), available_width)
}
ElementKind::EffectScope(scope) => {
if let Some(child) = scope.child.as_deref() {
update_split_wrap_width_hint(child, available_width);
}
}
ElementKind::MouseRegion(region) => {
if let Some(child) = region.child.as_deref() {
update_split_wrap_width_hint(child, available_width);
}
}
ElementKind::ContextProvider(provider) => {
update_split_wrap_width_hint(&provider.child, available_width)
}
_ => {}
}
}
pub(crate) fn split_wrap_pane_widths(
state: &SharedSplitWrapSync,
side: SplitPaneSide,
) -> Option<(u16, u16)> {
let state = state.borrow();
match side {
SplitPaneSide::Left => Some((state.left_pane_width?, state.right_pane_width?)),
SplitPaneSide::Right => Some((state.right_pane_width?, state.left_pane_width?)),
}
}
pub(crate) fn set_split_wrap_scrollbar_cols(
state: &SharedSplitWrapSync,
left_cols: u16,
right_cols: u16,
) {
let mut s = state.borrow_mut();
s.left_scrollbar_cols = left_cols;
s.right_scrollbar_cols = right_cols;
}
pub(crate) fn split_wrap_scrollbar_cols_pair(state: &SharedSplitWrapSync) -> (u16, u16) {
let s = state.borrow();
(s.left_scrollbar_cols, s.right_scrollbar_cols)
}
pub(crate) fn split_wrap_layout_pass(state: &SharedSplitWrapSync) -> u8 {
state.borrow().layout_pass
}
pub(crate) fn set_split_wrap_layout_pass(state: &SharedSplitWrapSync, pass: u8) {
state.borrow_mut().layout_pass = pass;
}
pub(crate) fn reset_split_wrap_layout_after_hstack(state: &SharedSplitWrapSync) {
let mut s = state.borrow_mut();
s.layout_pass = 0;
s.pass1_left_heights = None;
s.pass1_right_heights = None;
}
pub(crate) fn record_pass1_source_heights(
state: &SharedSplitWrapSync,
side: SplitPaneSide,
heights: &[u16],
) {
let mut s = state.borrow_mut();
let arc = Arc::new(heights.to_vec());
match side {
SplitPaneSide::Left => s.pass1_left_heights = Some(arc),
SplitPaneSide::Right => s.pass1_right_heights = Some(arc),
}
}
pub(crate) fn peer_pass1_source_heights(
state: &SharedSplitWrapSync,
side: SplitPaneSide,
) -> Option<Arc<Vec<u16>>> {
let s = state.borrow();
match side {
SplitPaneSide::Left => s.pass1_right_heights.clone(),
SplitPaneSide::Right => s.pass1_left_heights.clone(),
}
}
pub(crate) fn compute_split_wrap_padding_from_heights(
own_source_heights: &[u16],
peer_source_heights: &[u16],
) -> Vec<u16> {
if own_source_heights.is_empty() {
return Vec::new();
}
let mut padding = Vec::with_capacity(own_source_heights.len());
for (idx, own_h) in own_source_heights.iter().copied().enumerate() {
let peer_h = peer_source_heights.get(idx).copied().unwrap_or(0);
padding.push(peer_h.saturating_sub(own_h));
}
padding
}
pub(crate) fn split_wrap_sync_from_element(el: &Element) -> Option<SharedSplitWrapSync> {
match &el.kind {
ElementKind::DocumentView(d) => d.split_wrap_sync.clone(),
ElementKind::TextArea(t) => t.split_wrap_sync.clone(),
ElementKind::Frame(f) => f.child.as_deref().and_then(split_wrap_sync_from_element),
ElementKind::Group(g) => split_wrap_sync_from_element(g.child.as_ref()),
ElementKind::EffectScope(e) => e.child.as_deref().and_then(split_wrap_sync_from_element),
ElementKind::MouseRegion(m) => m.child.as_deref().and_then(split_wrap_sync_from_element),
ElementKind::ContextProvider(c) => split_wrap_sync_from_element(&c.child),
_ => None,
}
}
pub(crate) fn element_subtree_has_split_wrap_sync(el: &Element) -> bool {
if let Some(cached) = el.split_wrap_probe_cache.get() {
return cached;
}
let out = element_subtree_has_split_wrap_sync_impl(el);
el.split_wrap_probe_cache.set(Some(out));
out
}
fn element_subtree_has_split_wrap_sync_impl(el: &Element) -> bool {
match &el.kind {
ElementKind::DocumentView(d) => d.split_wrap_sync.is_some(),
ElementKind::TextArea(t) => t.split_wrap_sync.is_some(),
_ => el
.kind
.children()
.iter()
.any(|c| element_subtree_has_split_wrap_sync(c)),
}
}
pub(crate) fn shared_split_wrap_sync_for_dual_pass<'a>(
children: impl IntoIterator<Item = &'a Element>,
) -> Option<SharedSplitWrapSync> {
let mut collected: Vec<SharedSplitWrapSync> = Vec::new();
for c in children {
if let Some(s) = split_wrap_sync_from_element(c) {
collected.push(s);
}
}
let first = collected.first()?.clone();
if collected.len() >= 2 && collected.iter().all(|s| Rc::ptr_eq(&first, s)) {
Some(first)
} else {
None
}
}
pub(crate) fn peer_simulated_content_width(
state: &SharedSplitWrapSync,
side: SplitPaneSide,
own_content_w: u16,
) -> Option<u16> {
let (own_pane_w, peer_pane_w) = split_wrap_pane_widths(state, side)?;
let delta = peer_pane_w as i32 - own_pane_w as i32;
let st = state.borrow();
let sb_own = match side {
SplitPaneSide::Left => st.left_scrollbar_cols,
SplitPaneSide::Right => st.right_scrollbar_cols,
};
let sb_peer = match side {
SplitPaneSide::Left => st.right_scrollbar_cols,
SplitPaneSide::Right => st.left_scrollbar_cols,
};
let w = (own_content_w as i32 + delta - (sb_peer as i32 - sb_own as i32)).max(1) as u16;
Some(w)
}
pub(crate) fn compute_visual_line_count(text: &str, width: u16) -> u16 {
if width == 0 {
return 0;
}
if text.is_empty() {
return 1;
}
let spans = [Span::new(text)];
crate::utils::text::count_wrapped_lines_for_budgets(&spans, width, width) as u16
}
pub(crate) fn compute_split_wrap_padding(
own_source_heights: &[u16],
peer_lines: &[Arc<str>],
content_w: u16,
) -> Vec<u16> {
if own_source_heights.is_empty() {
return Vec::new();
}
let mut padding = Vec::with_capacity(own_source_heights.len());
for (idx, own_h) in own_source_heights.iter().copied().enumerate() {
let peer_h = peer_lines.get(idx).map_or(0, |line| {
compute_visual_line_count(line.as_ref(), content_w)
});
padding.push(peer_h.saturating_sub(own_h));
}
padding
}
#[cfg(test)]
mod tests {
use super::{
SplitPaneSide, compute_split_wrap_padding, compute_split_wrap_padding_from_heights,
compute_visual_line_count, new_split_wrap_sync_state, peer_simulated_content_width,
};
use std::sync::Arc;
#[test]
fn split_wrap_padding_simulation_covers_unicode_empty_and_overflow() {
assert_eq!(compute_visual_line_count("", 4), 1);
assert_eq!(compute_visual_line_count("abcdef", 3), 2);
assert_eq!(compute_visual_line_count("ab界d", 3), 2);
let own = vec![1, 2, 1];
let peer = vec![
Arc::<str>::from("abcd"),
Arc::<str>::from("ab"),
Arc::<str>::from("abcdef"),
];
assert_eq!(compute_split_wrap_padding(&own, &peer, 3), vec![1, 0, 1]);
}
#[test]
fn split_wrap_padding_from_heights_is_pairwise_saturating() {
assert_eq!(
compute_split_wrap_padding_from_heights(&[1, 3], &[2, 1]),
vec![1, 0]
);
}
#[test]
fn peer_sim_narrows_when_peer_has_extra_scrollbar_column() {
let sync = new_split_wrap_sync_state();
{
let mut s = sync.borrow_mut();
s.left_pane_width = Some(40);
s.right_pane_width = Some(40);
s.left_scrollbar_cols = 0;
s.right_scrollbar_cols = 1;
}
assert_eq!(
peer_simulated_content_width(&sync, SplitPaneSide::Left, 30).unwrap(),
29
);
assert_eq!(
peer_simulated_content_width(&sync, SplitPaneSide::Right, 29).unwrap(),
30
);
}
}