| 1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
|
| 8 |
|
| 9 |
|
| 10 |
|
| 11 |
|
| 12 |
|
| 13 |
|
| 14 |
|
| 15 |
|
| 16 |
|
| 17 |
|
| 18 |
|
| 19 |
|
| 20 |
|
| 21 |
|
| 22 |
|
| 23 |
use makeover_layout as layout; |
| 24 |
use makeover_tui::{frame, text}; |
| 25 |
use quasi_router::{Node, Ranked, RegionKind, Run, Screen, Slot}; |
| 26 |
use ratatui::buffer::Buffer; |
| 27 |
use ratatui::layout::Rect; |
| 28 |
use ratatui::style::{Modifier, Style}; |
| 29 |
use ratatui::text::Span; |
| 30 |
|
| 31 |
use crate::{Local, Pass, Tui, below}; |
| 32 |
|
| 33 |
|
| 34 |
|
| 35 |
|
| 36 |
|
| 37 |
|
| 38 |
|
| 39 |
|
| 40 |
|
| 41 |
|
| 42 |
|
| 43 |
|
| 44 |
|
| 45 |
|
| 46 |
|
| 47 |
|
| 48 |
|
| 49 |
|
| 50 |
|
| 51 |
|
| 52 |
|
| 53 |
|
| 54 |
|
| 55 |
|
| 56 |
fn cutoff(width: u16) -> layout::Priority { |
| 57 |
match width { |
| 58 |
0..60 => layout::Priority::Essential, |
| 59 |
60..84 => layout::Priority::Secondary, |
| 60 |
_ => layout::Priority::Optional, |
| 61 |
} |
| 62 |
} |
| 63 |
|
| 64 |
|
| 65 |
fn kept<'a>(body: &[&'a Ranked], width: u16) -> impl Iterator<Item = &'a Node> { |
| 66 |
let cutoff = cutoff(width); |
| 67 |
body.iter() |
| 68 |
.copied() |
| 69 |
.filter(move |placed| placed.kept_at(cutoff)) |
| 70 |
.map(|placed| &placed.node) |
| 71 |
} |
| 72 |
|
| 73 |
|
| 74 |
pub(crate) fn screen_regions(pass: &mut Pass<'_>, screen: &Screen, area: Rect, buf: &mut Buffer) { |
| 75 |
let area = measured(screen.measure, area); |
| 76 |
let mut rest = area; |
| 77 |
|
| 78 |
|
| 79 |
|
| 80 |
|
| 81 |
|
| 82 |
|
| 83 |
|
| 84 |
|
| 85 |
let (leading, trailing) = bands(screen); |
| 86 |
for slot in &leading { |
| 87 |
let used = draw(pass, slot, rest, buf); |
| 88 |
rest = below(rest, used); |
| 89 |
} |
| 90 |
|
| 91 |
|
| 92 |
|
| 93 |
|
| 94 |
|
| 95 |
|
| 96 |
let reserved = trailing |
| 97 |
.iter() |
| 98 |
.map(|slot| height(pass.tui, slot, rest.width, &pass.local())) |
| 99 |
.sum::<u16>() |
| 100 |
.min(rest.height); |
| 101 |
let mut feet = below(rest, rest.height - reserved); |
| 102 |
rest = Rect { |
| 103 |
height: rest.height - reserved, |
| 104 |
..rest |
| 105 |
}; |
| 106 |
|
| 107 |
let body = body_slots(screen); |
| 108 |
|
| 109 |
match screen.arrangement { |
| 110 |
layout::Arrangement::SidebarContent { share } => { |
| 111 |
let (left, right) = split(rest, share.of(rest.width)); |
| 112 |
let mut sidebars = 0; |
| 113 |
let mut content = right; |
| 114 |
for slot in &body { |
| 115 |
if matches!(slot.kind, RegionKind::Sidebar) { |
| 116 |
let used = draw(pass, slot, below(left, sidebars), buf); |
| 117 |
sidebars += used; |
| 118 |
} else { |
| 119 |
let used = draw(pass, slot, content, buf); |
| 120 |
content = below(content, used); |
| 121 |
} |
| 122 |
} |
| 123 |
} |
| 124 |
layout::Arrangement::ListDetail { tabbed, share } => { |
| 125 |
if tabbed { |
| 126 |
|
| 127 |
|
| 128 |
if let Some(first) = body.first() { |
| 129 |
draw(pass, first, rest, buf); |
| 130 |
} |
| 131 |
} else { |
| 132 |
let (left, right) = split(rest, share.of(rest.width)); |
| 133 |
let mut detail = right; |
| 134 |
for (index, slot) in body.iter().enumerate() { |
| 135 |
if index == 0 { |
| 136 |
draw(pass, slot, left, buf); |
| 137 |
} else { |
| 138 |
let used = draw(pass, slot, detail, buf); |
| 139 |
detail = below(detail, used); |
| 140 |
} |
| 141 |
} |
| 142 |
} |
| 143 |
} |
| 144 |
|
| 145 |
|
| 146 |
layout::Arrangement::Single => { |
| 147 |
let mut content = rest; |
| 148 |
for slot in &body { |
| 149 |
let used = draw(pass, slot, content, buf); |
| 150 |
content = below(content, used); |
| 151 |
} |
| 152 |
} |
| 153 |
} |
| 154 |
|
| 155 |
|
| 156 |
|
| 157 |
for slot in &trailing { |
| 158 |
let used = draw(pass, slot, feet, buf); |
| 159 |
feet = below(feet, used); |
| 160 |
} |
| 161 |
|
| 162 |
|
| 163 |
|
| 164 |
|
| 165 |
for slot in screen |
| 166 |
.slots |
| 167 |
.iter() |
| 168 |
.filter(|slot| matches!(slot.kind, RegionKind::Modal)) |
| 169 |
{ |
| 170 |
draw(pass, slot, centred(area), buf); |
| 171 |
} |
| 172 |
} |
| 173 |
|
| 174 |
|
| 175 |
|
| 176 |
|
| 177 |
|
| 178 |
|
| 179 |
|
| 180 |
|
| 181 |
|
| 182 |
|
| 183 |
|
| 184 |
|
| 185 |
|
| 186 |
|
| 187 |
|
| 188 |
|
| 189 |
fn measured(measure: layout::Measure, area: Rect) -> Rect { |
| 190 |
let cap = match measure { |
| 191 |
layout::Measure::Reading => 76, |
| 192 |
layout::Measure::Contained => 100, |
| 193 |
|
| 194 |
|
| 195 |
|
| 196 |
_ => return area, |
| 197 |
}; |
| 198 |
if area.width <= cap { |
| 199 |
return area; |
| 200 |
} |
| 201 |
Rect { |
| 202 |
x: area.x + (area.width - cap) / 2, |
| 203 |
width: cap, |
| 204 |
..area |
| 205 |
} |
| 206 |
} |
| 207 |
|
| 208 |
|
| 209 |
|
| 210 |
|
| 211 |
|
| 212 |
|
| 213 |
|
| 214 |
fn body_slots(screen: &Screen) -> Vec<&Slot> { |
| 215 |
let body = screen |
| 216 |
.slots |
| 217 |
.iter() |
| 218 |
.filter(|slot| !matches!(slot.kind, RegionKind::Band | RegionKind::Modal)); |
| 219 |
|
| 220 |
match screen.arrangement { |
| 221 |
layout::Arrangement::ListDetail { tabbed: true, .. } => body.take(1).collect(), |
| 222 |
_ => body.collect(), |
| 223 |
} |
| 224 |
} |
| 225 |
|
| 226 |
|
| 227 |
|
| 228 |
|
| 229 |
|
| 230 |
|
| 231 |
fn bands(screen: &Screen) -> (Vec<&Slot>, Vec<&Slot>) { |
| 232 |
let is_band = |slot: &&Slot| matches!(slot.kind, RegionKind::Band); |
| 233 |
let first_body = screen |
| 234 |
.slots |
| 235 |
.iter() |
| 236 |
.position(|slot| !matches!(slot.kind, RegionKind::Band | RegionKind::Modal)); |
| 237 |
let Some(at) = first_body else { |
| 238 |
return (screen.slots.iter().filter(is_band).collect(), Vec::new()); |
| 239 |
}; |
| 240 |
( |
| 241 |
screen.slots[..at].iter().filter(is_band).collect(), |
| 242 |
screen.slots[at..].iter().filter(is_band).collect(), |
| 243 |
) |
| 244 |
} |
| 245 |
|
| 246 |
|
| 247 |
|
| 248 |
|
| 249 |
|
| 250 |
|
| 251 |
|
| 252 |
|
| 253 |
|
| 254 |
|
| 255 |
|
| 256 |
|
| 257 |
pub(crate) fn reachable(screen: &Screen) -> Vec<&Slot> { |
| 258 |
let modals: Vec<&Slot> = screen |
| 259 |
.slots |
| 260 |
.iter() |
| 261 |
.filter(|slot| matches!(slot.kind, RegionKind::Modal)) |
| 262 |
.collect(); |
| 263 |
if !modals.is_empty() { |
| 264 |
return modals; |
| 265 |
} |
| 266 |
|
| 267 |
let (leading, trailing) = bands(screen); |
| 268 |
leading |
| 269 |
.into_iter() |
| 270 |
.chain(body_slots(screen)) |
| 271 |
.chain(trailing) |
| 272 |
.collect() |
| 273 |
} |
| 274 |
|
| 275 |
|
| 276 |
|
| 277 |
|
| 278 |
|
| 279 |
|
| 280 |
|
| 281 |
|
| 282 |
|
| 283 |
|
| 284 |
|
| 285 |
|
| 286 |
|
| 287 |
|
| 288 |
|
| 289 |
|
| 290 |
|
| 291 |
|
| 292 |
|
| 293 |
|
| 294 |
|
| 295 |
|
| 296 |
|
| 297 |
|
| 298 |
|
| 299 |
|
| 300 |
|
| 301 |
|
| 302 |
|
| 303 |
fn packed<'a>(tui: &Tui, run: &'a Run, width: u16) -> Vec<Vec<(&'a Node, u16)>> { |
| 304 |
let kept: Vec<&Ranked> = match run.fallback { |
| 305 |
layout::Fallback::Shed => run.kept_at(cutoff(width)), |
| 306 |
_ => run.members.iter().collect(), |
| 307 |
}; |
| 308 |
|
| 309 |
let mut lines: Vec<Vec<(&Ranked, u16)>> = Vec::new(); |
| 310 |
let mut line: Vec<(&Ranked, u16)> = Vec::new(); |
| 311 |
let mut left = width; |
| 312 |
for placed in kept { |
| 313 |
let wants = match crate::node::want(tui, &placed.node) { |
| 314 |
crate::node::Want::Cells(cells) => cells.min(width), |
| 315 |
crate::node::Want::Rest => left.max(MEMBER_FLOOR).min(width), |
| 316 |
}; |
| 317 |
let gap = u16::from(!line.is_empty()); |
| 318 |
if !line.is_empty() && wants + gap > left { |
| 319 |
lines.push(std::mem::take(&mut line)); |
| 320 |
left = width; |
| 321 |
} |
| 322 |
let gap = u16::from(!line.is_empty()); |
| 323 |
let given = wants.min(left.saturating_sub(gap)); |
| 324 |
line.push((placed, given)); |
| 325 |
left = left.saturating_sub(given + gap); |
| 326 |
} |
| 327 |
if !line.is_empty() { |
| 328 |
lines.push(line); |
| 329 |
} |
| 330 |
|
| 331 |
lines.into_iter().map(|line| filled(line, width)).collect() |
| 332 |
} |
| 333 |
|
| 334 |
|
| 335 |
|
| 336 |
|
| 337 |
|
| 338 |
|
| 339 |
|
| 340 |
|
| 341 |
|
| 342 |
|
| 343 |
|
| 344 |
|
| 345 |
|
| 346 |
|
| 347 |
|
| 348 |
|
| 349 |
|
| 350 |
fn filled(line: Vec<(&Ranked, u16)>, width: u16) -> Vec<(&Node, u16)> { |
| 351 |
let gaps = u16::try_from(line.len().saturating_sub(1)).unwrap_or(u16::MAX); |
| 352 |
let taken: u16 = line.iter().map(|(_, given)| *given).sum::<u16>() + gaps; |
| 353 |
let left = width.saturating_sub(taken); |
| 354 |
let fills = u16::try_from( |
| 355 |
line.iter() |
| 356 |
.filter(|(placed, _)| matches!(placed.width, layout::Width::Fill)) |
| 357 |
.count(), |
| 358 |
) |
| 359 |
.unwrap_or(u16::MAX); |
| 360 |
if left == 0 || fills == 0 { |
| 361 |
return line |
| 362 |
.into_iter() |
| 363 |
.map(|(placed, given)| (&placed.node, given)) |
| 364 |
.collect(); |
| 365 |
} |
| 366 |
|
| 367 |
|
| 368 |
|
| 369 |
|
| 370 |
let share = left / fills; |
| 371 |
let mut over = left % fills; |
| 372 |
line.into_iter() |
| 373 |
.map(|(placed, given)| { |
| 374 |
if !matches!(placed.width, layout::Width::Fill) { |
| 375 |
return (&placed.node, given); |
| 376 |
} |
| 377 |
let extra = share + u16::from(over > 0); |
| 378 |
over = over.saturating_sub(1); |
| 379 |
(&placed.node, given + extra) |
| 380 |
}) |
| 381 |
.collect() |
| 382 |
} |
| 383 |
|
| 384 |
|
| 385 |
|
| 386 |
|
| 387 |
|
| 388 |
|
| 389 |
const MEMBER_FLOOR: u16 = 8; |
| 390 |
|
| 391 |
|
| 392 |
fn run_height(tui: &Tui, slot: &Slot, width: u16, local: &Local<'_>) -> u16 { |
| 393 |
let Some(run) = slot.run.as_ref() else { |
| 394 |
return 0; |
| 395 |
}; |
| 396 |
packed(tui, run, width) |
| 397 |
.iter() |
| 398 |
.map(|line| { |
| 399 |
line.iter() |
| 400 |
.map(|(node, cells)| crate::node::height(tui, node, *cells, local)) |
| 401 |
.max() |
| 402 |
.unwrap_or(0) |
| 403 |
}) |
| 404 |
.sum() |
| 405 |
} |
| 406 |
|
| 407 |
|
| 408 |
fn run_body(pass: &mut Pass<'_>, slot: &Slot, inner: Rect, buf: &mut Buffer) -> u16 { |
| 409 |
let Some(run) = slot.run.as_ref() else { |
| 410 |
return 0; |
| 411 |
}; |
| 412 |
let lines = packed(pass.tui, run, inner.width); |
| 413 |
let mut used = 0; |
| 414 |
for line in lines { |
| 415 |
let area = below(inner, used); |
| 416 |
if area.height == 0 { |
| 417 |
break; |
| 418 |
} |
| 419 |
let mut column = 0; |
| 420 |
let mut tall = 0; |
| 421 |
for (node, cells) in line { |
| 422 |
let cell = Rect { |
| 423 |
x: area.x + column, |
| 424 |
width: cells.min(area.width.saturating_sub(column)), |
| 425 |
..area |
| 426 |
}; |
| 427 |
if cell.width == 0 { |
| 428 |
break; |
| 429 |
} |
| 430 |
tall = tall.max(crate::node::draw(pass, node, cell, buf)); |
| 431 |
|
| 432 |
|
| 433 |
column += cell.width + 1; |
| 434 |
} |
| 435 |
used += tall.max(1); |
| 436 |
} |
| 437 |
used.min(inner.height) |
| 438 |
} |
| 439 |
|
| 440 |
|
| 441 |
pub(crate) fn height(tui: &Tui, slot: &Slot, width: u16, local: &Local<'_>) -> u16 { |
| 442 |
|
| 443 |
|
| 444 |
|
| 445 |
|
| 446 |
if local.out(&slot.id) { |
| 447 |
return 0; |
| 448 |
} |
| 449 |
let inner = width.saturating_sub(2); |
| 450 |
|
| 451 |
|
| 452 |
|
| 453 |
|
| 454 |
|
| 455 |
let body: u16 = if slot.showing().selective() { |
| 456 |
|
| 457 |
|
| 458 |
|
| 459 |
slot.body |
| 460 |
.iter() |
| 461 |
.map(|placed| crate::node::height(tui, &placed.node, inner, local)) |
| 462 |
.max() |
| 463 |
.unwrap_or(0) |
| 464 |
+ 1 |
| 465 |
} else if slot.repeating.is_some() { |
| 466 |
|
| 467 |
|
| 468 |
|
| 469 |
slot.body |
| 470 |
.iter() |
| 471 |
.map(|placed| { |
| 472 |
1 + crate::node::height(tui, &placed.node, inner, local) |
| 473 |
+ u16::from(removes_of(&placed.node).is_some()) |
| 474 |
}) |
| 475 |
.sum::<u16>() |
| 476 |
+ 1 |
| 477 |
} else { |
| 478 |
|
| 479 |
|
| 480 |
kept(&slot.body.members().collect::<Vec<_>>(), inner) |
| 481 |
.map(|node| crate::node::height(tui, node, inner, local)) |
| 482 |
.sum() |
| 483 |
}; |
| 484 |
|
| 485 |
|
| 486 |
|
| 487 |
let filled = match (&slot.kind, tui.fill(&slot.id)) { |
| 488 |
(RegionKind::Handover { .. } | RegionKind::Ceded { .. }, Some(fill)) => { |
| 489 |
fill.rows(tui, inner) |
| 490 |
} |
| 491 |
|
| 492 |
|
| 493 |
(RegionKind::Handover { .. }, None) => text::height(crate::node::UNFILLED, inner), |
| 494 |
_ => 0, |
| 495 |
}; |
| 496 |
|
| 497 |
|
| 498 |
run_height(tui, slot, inner, local) |
| 499 |
+ body |
| 500 |
+ filled |
| 501 |
+ if framed(slot.kind.depth()) { 2 } else { 0 } |
| 502 |
} |
| 503 |
|
| 504 |
|
| 505 |
pub(crate) fn draw(pass: &mut Pass<'_>, slot: &Slot, area: Rect, buf: &mut Buffer) -> u16 { |
| 506 |
|
| 507 |
|
| 508 |
|
| 509 |
|
| 510 |
|
| 511 |
|
| 512 |
|
| 513 |
|
| 514 |
|
| 515 |
if pass.local().out(&slot.id) { |
| 516 |
return 0; |
| 517 |
} |
| 518 |
|
| 519 |
|
| 520 |
|
| 521 |
|
| 522 |
let pending = matches!(slot.readiness, layout::Readiness::Pending); |
| 523 |
|
| 524 |
if area.width == 0 || area.height == 0 { |
| 525 |
if !pending { |
| 526 |
for placed in slot |
| 527 |
.run |
| 528 |
.iter() |
| 529 |
.flat_map(|run| run.members.iter()) |
| 530 |
.chain(slot.body.iter()) |
| 531 |
{ |
| 532 |
crate::node::draw(pass, &placed.node, area, buf); |
| 533 |
} |
| 534 |
} |
| 535 |
return 0; |
| 536 |
} |
| 537 |
|
| 538 |
let tui = pass.tui; |
| 539 |
|
| 540 |
|
| 541 |
|
| 542 |
|
| 543 |
|
| 544 |
let depth = slot.kind.depth(); |
| 545 |
let inner = if framed(depth) { |
| 546 |
frame(buf, area, depth, tui.palette()) |
| 547 |
} else { |
| 548 |
area |
| 549 |
}; |
| 550 |
|
| 551 |
|
| 552 |
|
| 553 |
|
| 554 |
if pending { |
| 555 |
let used = text::draw( |
| 556 |
"Loading", |
| 557 |
Style::default() |
| 558 |
.fg(tui.theme().content_muted) |
| 559 |
.add_modifier(Modifier::ITALIC), |
| 560 |
inner, |
| 561 |
buf, |
| 562 |
); |
| 563 |
return used + if framed(depth) { 2 } else { 0 }; |
| 564 |
} |
| 565 |
|
| 566 |
|
| 567 |
|
| 568 |
|
| 569 |
|
| 570 |
|
| 571 |
|
| 572 |
|
| 573 |
|
| 574 |
|
| 575 |
|
| 576 |
|
| 577 |
|
| 578 |
let row = run_body(pass, slot, inner, buf); |
| 579 |
let rest = below(inner, row); |
| 580 |
let mut used = row |
| 581 |
+ if slot.showing().selective() { |
| 582 |
showing_body(pass, slot, rest, buf) |
| 583 |
} else if let Some(repeating) = slot.repeating.as_deref() { |
| 584 |
repeating_body(pass, slot, repeating, rest, buf) |
| 585 |
} else { |
| 586 |
body( |
| 587 |
pass, |
| 588 |
slot, |
| 589 |
&slot.body.members().collect::<Vec<_>>(), |
| 590 |
rest, |
| 591 |
buf, |
| 592 |
) |
| 593 |
}; |
| 594 |
|
| 595 |
if let RegionKind::Handover { .. } | RegionKind::Ceded { .. } = slot.kind { |
| 596 |
if let Some(fill) = tui.fill(&slot.id) { |
| 597 |
used = (used + fill.draw(tui, below(inner, used), buf)).min(inner.height); |
| 598 |
} else if slot.kind.as_layout().owed() { |
| 599 |
|
| 600 |
|
| 601 |
|
| 602 |
let drawn = text::draw( |
| 603 |
crate::node::UNFILLED, |
| 604 |
tui.style().muted, |
| 605 |
below(inner, used), |
| 606 |
buf, |
| 607 |
); |
| 608 |
used = (used + drawn).min(inner.height); |
| 609 |
} |
| 610 |
} |
| 611 |
|
| 612 |
|
| 613 |
|
| 614 |
|
| 615 |
used + if framed(depth) { 2 } else { 0 } |
| 616 |
} |
| 617 |
|
| 618 |
|
| 619 |
|
| 620 |
|
| 621 |
|
| 622 |
|
| 623 |
pub(crate) fn removes_of(node: &Node) -> Option<&quasi_router::Act> { |
| 624 |
match node { |
| 625 |
Node::Region(child) => child.removes.as_deref(), |
| 626 |
_ => None, |
| 627 |
} |
| 628 |
} |
| 629 |
|
| 630 |
|
| 631 |
|
| 632 |
|
| 633 |
|
| 634 |
|
| 635 |
|
| 636 |
pub(crate) fn bounded(act: &quasi_router::Act, allowed: bool) -> quasi_router::Act { |
| 637 |
if allowed { |
| 638 |
act.clone() |
| 639 |
} else { |
| 640 |
act.clone().disabled() |
| 641 |
} |
| 642 |
} |
| 643 |
|
| 644 |
|
| 645 |
|
| 646 |
|
| 647 |
|
| 648 |
|
| 649 |
|
| 650 |
|
| 651 |
|
| 652 |
|
| 653 |
|
| 654 |
fn repeating_body( |
| 655 |
pass: &mut Pass<'_>, |
| 656 |
slot: &Slot, |
| 657 |
repeating: &quasi_router::Repeating, |
| 658 |
inner: Rect, |
| 659 |
buf: &mut Buffer, |
| 660 |
) -> u16 { |
| 661 |
let standing = slot.body.len(); |
| 662 |
let mut used = 0; |
| 663 |
for (at, placed) in slot.body.iter().enumerate() { |
| 664 |
|
| 665 |
used += crate::node::draw( |
| 666 |
pass, |
| 667 |
&Node::section(format!("{} {}", repeating.one, at + 1)), |
| 668 |
below(inner, used), |
| 669 |
buf, |
| 670 |
); |
| 671 |
used += crate::node::draw(pass, &placed.node, below(inner, used), buf); |
| 672 |
if let Some(removes) = removes_of(&placed.node) { |
| 673 |
let act = Node::Act(bounded(removes, repeating.may_remove(standing))); |
| 674 |
used += crate::node::draw(pass, &act, below(inner, used), buf); |
| 675 |
} |
| 676 |
} |
| 677 |
let add = Node::Act(bounded(&repeating.add, repeating.may_add(standing))); |
| 678 |
used += crate::node::draw(pass, &add, below(inner, used), buf); |
| 679 |
used.min(inner.height) |
| 680 |
} |
| 681 |
|
| 682 |
|
| 683 |
|
| 684 |
|
| 685 |
|
| 686 |
|
| 687 |
|
| 688 |
|
| 689 |
|
| 690 |
|
| 691 |
|
| 692 |
|
| 693 |
|
| 694 |
|
| 695 |
|
| 696 |
fn showing_body(pass: &mut Pass<'_>, slot: &Slot, inner: Rect, buf: &mut Buffer) -> u16 { |
| 697 |
let at = pass.view.shown(slot); |
| 698 |
let labels = slot.labels(); |
| 699 |
let mut used = 0; |
| 700 |
|
| 701 |
|
| 702 |
|
| 703 |
|
| 704 |
if !labels.is_empty() { |
| 705 |
used += text::draw_spans( |
| 706 |
&showing_spans(pass.tui, &labels, at), |
| 707 |
below(inner, used), |
| 708 |
buf, |
| 709 |
); |
| 710 |
} |
| 711 |
|
| 712 |
|
| 713 |
|
| 714 |
if let Some(index) = at |
| 715 |
&& index < slot.body.len() |
| 716 |
{ |
| 717 |
|
| 718 |
|
| 719 |
if let Some(member) = slot.body.get(index) { |
| 720 |
used += body(pass, slot, &[member], below(inner, used), buf); |
| 721 |
} |
| 722 |
} |
| 723 |
|
| 724 |
if labels.is_empty() { |
| 725 |
used += text::draw_spans( |
| 726 |
&counter_spans(pass.tui, at, slot.body.len()), |
| 727 |
below(inner, used), |
| 728 |
buf, |
| 729 |
); |
| 730 |
} |
| 731 |
|
| 732 |
used.min(inner.height) |
| 733 |
} |
| 734 |
|
| 735 |
|
| 736 |
|
| 737 |
|
| 738 |
fn showing_spans(tui: &Tui, labels: &[&str], at: Option<usize>) -> Vec<Span<'static>> { |
| 739 |
let mut spans = Vec::new(); |
| 740 |
for (index, label) in labels.iter().enumerate() { |
| 741 |
if !spans.is_empty() { |
| 742 |
spans.push(Span::raw(" ")); |
| 743 |
} |
| 744 |
let picked = at == Some(index); |
| 745 |
let style = if picked { |
| 746 |
Style::default() |
| 747 |
.fg(tui.theme().selection_on) |
| 748 |
.bg(tui.theme().action_primary) |
| 749 |
} else { |
| 750 |
Style::default().fg(tui.theme().content_secondary) |
| 751 |
}; |
| 752 |
spans.push(Span::styled(format!(" {label} "), style)); |
| 753 |
} |
| 754 |
spans |
| 755 |
} |
| 756 |
|
| 757 |
|
| 758 |
|
| 759 |
|
| 760 |
|
| 761 |
|
| 762 |
|
| 763 |
fn counter_spans(tui: &Tui, at: Option<usize>, total: usize) -> Vec<Span<'static>> { |
| 764 |
let control = Style::default().fg(tui.theme().content_secondary); |
| 765 |
vec![ |
| 766 |
Span::styled("< Prev >", control), |
| 767 |
Span::styled( |
| 768 |
format!(" {} / {total} ", at.map_or(0, |index| index + 1)), |
| 769 |
Style::default().fg(tui.theme().content_muted), |
| 770 |
), |
| 771 |
Span::styled("< Next >", control), |
| 772 |
] |
| 773 |
} |
| 774 |
|
| 775 |
|
| 776 |
|
| 777 |
|
| 778 |
|
| 779 |
|
| 780 |
|
| 781 |
|
| 782 |
|
| 783 |
|
| 784 |
|
| 785 |
|
| 786 |
|
| 787 |
|
| 788 |
|
| 789 |
|
| 790 |
|
| 791 |
|
| 792 |
|
| 793 |
|
| 794 |
|
| 795 |
fn body(pass: &mut Pass<'_>, slot: &Slot, nodes: &[&Ranked], inner: Rect, buf: &mut Buffer) -> u16 { |
| 796 |
let offset = pass.view.scroll(&slot.id); |
| 797 |
if offset == 0 { |
| 798 |
let mut used = 0; |
| 799 |
for node in kept(nodes, inner.width) { |
| 800 |
used += crate::node::draw(pass, node, below(inner, used), buf); |
| 801 |
} |
| 802 |
return used.min(inner.height); |
| 803 |
} |
| 804 |
|
| 805 |
let content: u16 = kept(nodes, inner.width) |
| 806 |
.map(|node| crate::node::height(pass.tui, node, inner.width, &pass.local())) |
| 807 |
.sum(); |
| 808 |
let offset = offset.min(content.saturating_sub(inner.height)); |
| 809 |
|
| 810 |
let tall = Rect { |
| 811 |
height: content.max(inner.height), |
| 812 |
..inner |
| 813 |
}; |
| 814 |
let mut scratch = Buffer::empty(tall); |
| 815 |
let mut used = 0; |
| 816 |
for node in kept(nodes, inner.width) { |
| 817 |
used += crate::node::draw(pass, node, below(tall, used), &mut scratch); |
| 818 |
} |
| 819 |
|
| 820 |
let shown = inner.height.min(content.saturating_sub(offset)); |
| 821 |
for row in 0..shown { |
| 822 |
for column in 0..inner.width { |
| 823 |
let from = (inner.x + column, inner.y + offset + row); |
| 824 |
let to = (inner.x + column, inner.y + row); |
| 825 |
if let Some(cell) = scratch.cell(from).cloned() |
| 826 |
&& let Some(target) = buf.cell_mut(to) |
| 827 |
{ |
| 828 |
*target = cell; |
| 829 |
} |
| 830 |
} |
| 831 |
} |
| 832 |
shown |
| 833 |
} |
| 834 |
|
| 835 |
|
| 836 |
|
| 837 |
|
| 838 |
|
| 839 |
|
| 840 |
fn framed(depth: layout::Depth) -> bool { |
| 841 |
!matches!(depth, layout::Depth::Flat) |
| 842 |
} |
| 843 |
|
| 844 |
|
| 845 |
fn split(area: Rect, width: u16) -> (Rect, Rect) { |
| 846 |
let width = width.min(area.width); |
| 847 |
( |
| 848 |
Rect { width, ..area }, |
| 849 |
Rect { |
| 850 |
x: area.x + width, |
| 851 |
width: area.width - width, |
| 852 |
..area |
| 853 |
}, |
| 854 |
) |
| 855 |
} |
| 856 |
|
| 857 |
|
| 858 |
fn centred(area: Rect) -> Rect { |
| 859 |
let width = area.width / 2; |
| 860 |
let height = area.height / 2; |
| 861 |
Rect { |
| 862 |
x: area.x + width / 2, |
| 863 |
y: area.y + height / 2, |
| 864 |
width, |
| 865 |
height, |
| 866 |
} |
| 867 |
} |
| 868 |
|