use crate::AppModel;
use crate::cli_outbound::Cli;
use crate::keymap::CenterTab;
use crate::nav::{self, menu::Seat};
use super::ShellState;
use super::menus::{BallRef, Target};
pub fn side_panel(ui: &mut egui::Ui, model: &mut AppModel, state: &mut ShellState, lernie: &Cli) {
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Truncate);
let column = ui.available_height();
if crate::layout::share(column, column, 0.0).is_some_and(|cap| cap >= DOORS) {
entries(ui, model, state);
if let Some(cap) = crate::layout::share(column, ui.available_height(), 0.0) {
sections(ui, cap, model, state, lernie);
}
}
super::conv_list::conversations(ui, model, state, lernie);
}
const DOORS: f32 = 3.0 * crate::layout::ROW;
fn entries(ui: &mut egui::Ui, model: &mut AppModel, state: &mut ShellState) {
egui::TopBottomPanel::bottom("navigator-entries").show_inside(ui, |ui| {
for tab in [CenterTab::Config, CenterTab::Login] {
let label = format!("{}{}", entry_mark(tab), tab.label());
if ui
.selectable_label(state.center == tab, label)
.on_hover_text(tab.focus_hover())
.clicked()
{
super::center::focus(model, state, tab);
}
}
});
}
fn sections(
ui: &mut egui::Ui,
cap: f32,
model: &mut AppModel,
state: &mut ShellState,
lernie: &Cli,
) {
egui::TopBottomPanel::bottom("navigator-sections").show_inside(ui, |ui| {
ui.set_max_height(cap);
egui::ScrollArea::vertical()
.id_salt("navigator-sections")
.max_height(cap)
.show(ui, |ui| {
super::row::shown_width(ui);
balls_section(ui, model, state, lernie);
super::clients::section(ui, model);
});
});
}
fn entry_mark(tab: CenterTab) -> &'static str {
match tab {
CenterTab::Config => "⚙ ",
_ => "",
}
}
fn balls_section(ui: &mut egui::Ui, model: &mut AppModel, state: &mut ShellState, lernie: &Cli) {
let collapsed = model.is_collapsed("balls");
let arrow = if collapsed { "▶" } else { "▼" };
if ui
.selectable_label(false, format!("{arrow} balls"))
.on_hover_text(
"show or hide the balls section — the ready tasks you can start and the \
balls this workspace already holds (b)",
)
.clicked()
{
model.set_collapsed("balls", !collapsed);
}
if collapsed {
return;
}
ui.indent("balls", |ui| {
super::board::board(ui, model, state, lernie);
for ball in nav::balls::roster(&super::chrome::focused_balls(model)) {
bound_ball_row(ui, model, state, lernie, &ball);
}
});
}
fn bound_ball_row(
ui: &mut egui::Ui,
model: &mut AppModel,
state: &mut ShellState,
lernie: &Cli,
ball: &crate::nav::BoundBall,
) {
let text = match &ball.badge {
Some(b) => format!("{} · {b}", ball.id),
None => ball.id.clone(),
};
let row = ui
.add(egui::Label::new(egui::RichText::new(text).weak()).sense(egui::Sense::click()))
.on_hover_text(
"a ball this workspace has claimed. Right-click for its actions — Close \
and Release. Each is also a key or a line: (c), (r), `/close [id]`, \
`/release [id]`.",
);
let seat = Seat::BallRow {
state: ball.state,
assign_to: model.focused_ws_name(),
};
let target = Target::Ball(BallRef {
project: ball.project.clone(),
id: ball.id.clone(),
owner: ball.owner.clone(),
});
super::menus::attach(&row, seat, &target, model, state, lernie);
}