mod activity;
mod ball_bar;
mod config_edit;
mod config_marks;
mod conv_ball;
mod fire;
mod input_bar;
mod inspector;
mod navigator;
mod start_pane;
mod toolchain;
mod workspace;
#[cfg(test)]
mod acceptance;
pub use config_edit::ConfigState;
use crate::AppModel;
use crate::actions::ActionsState;
use crate::cli_outbound::Cli;
use crate::keymap::{self, Key, KeyAction};
use crate::start::Prepared;
use crate::steps_view::StepTab;
use crate::xdg::Env;
use std::collections::{HashMap, HashSet};
use std::path::PathBuf;
#[derive(Default)]
pub struct StartState {
pub new_ball: HashMap<PathBuf, (String, String)>,
pub pending: Option<Prepared>,
pub mint_seed: u64,
pub last_failure: Option<crate::opslog::SurfaceFailure>,
}
pub struct InspectorState {
pub raw: bool,
pub step_sel: Option<usize>,
pub step_tab: StepTab,
pub json_collapsed: HashSet<String>,
pub files_sel: Option<usize>,
}
impl Default for InspectorState {
fn default() -> Self {
Self {
raw: false,
step_sel: None,
step_tab: StepTab::Meta,
json_collapsed: HashSet::new(),
files_sel: None,
}
}
}
#[derive(Default)]
pub struct LoginHolder {
pub providers: Vec<String>,
pub run: Option<crate::login::LoginRun>,
}
pub struct ShellState {
pub actions: ActionsState,
pub start: StartState,
pub inspector: InspectorState,
pub config: ConfigState,
pub login: LoginHolder,
pub focus_composer: bool,
pub group_by_ball: bool,
}
impl ShellState {
pub fn new(env: &Env) -> std::io::Result<Self> {
Ok(Self {
actions: ActionsState::default(),
start: StartState {
mint_seed: entropy_seed(),
..StartState::default()
},
inspector: InspectorState::default(),
config: ConfigState::new(env)?,
login: LoginHolder::default(),
focus_composer: false,
group_by_ball: false,
})
}
}
pub fn render(
ctx: &egui::Context,
model: &mut AppModel,
state: &mut ShellState,
lernie: &Cli,
bl: &Cli,
bz: &Cli,
) {
handle_keys(ctx, model);
egui::TopBottomPanel::top("top-bar")
.show(ctx, |ui| navigator::top_bar(ui, model, state, lernie, bl));
egui::TopBottomPanel::bottom("activity").show(ctx, |ui| activity::accessory(ui, model));
let composer_open =
model.focused_workspace().is_some() && !model.focused_is_replay() && !state.config.active;
if composer_open {
egui::TopBottomPanel::bottom("composer")
.default_height(96.0)
.show(ctx, |ui| {
input_bar::composer(ui, model, state, lernie, bl);
});
}
if state.start.pending.is_some() {
egui::TopBottomPanel::bottom("start-composer")
.default_height(240.0)
.show(ctx, |ui| {
start_pane::composer(ui, model, &mut state.start, lernie);
});
}
egui::SidePanel::left("conversations")
.default_width(260.0)
.show(ctx, |ui| {
navigator::side_panel(ui, model, state, lernie, bl, bz);
});
egui::CentralPanel::default().show(ctx, |ui| {
if state.config.active {
config_edit::center(ui, model, &mut state.config, lernie);
} else {
workspace::center(ui, model, state, lernie, bl, bz);
}
});
}
const DIGIT_KEYS: [(egui::Key, u8); 9] = [
(egui::Key::Num1, 1),
(egui::Key::Num2, 2),
(egui::Key::Num3, 3),
(egui::Key::Num4, 4),
(egui::Key::Num5, 5),
(egui::Key::Num6, 6),
(egui::Key::Num7, 7),
(egui::Key::Num8, 8),
(egui::Key::Num9, 9),
];
fn handle_keys(ctx: &egui::Context, model: &mut AppModel) {
if ctx.wants_keyboard_input() {
return;
}
let pressed: Vec<Key> = ctx.input(|i| {
let mut keys = Vec::new();
if i.key_pressed(egui::Key::ArrowUp) {
keys.push(Key::Up);
}
if i.key_pressed(egui::Key::ArrowDown) {
keys.push(Key::Down);
}
for (egui_key, n) in DIGIT_KEYS {
if i.key_pressed(egui_key) {
keys.push(Key::Digit(n));
}
}
keys
});
for action in pressed.into_iter().filter_map(keymap::keymap) {
match action {
KeyAction::RosterPrev => model.roster_step(-1),
KeyAction::RosterNext => model.roster_step(1),
KeyAction::Tab(tab) => model.select_tab(tab),
}
}
}
pub(super) fn failure_banner(ui: &mut egui::Ui, failure: &crate::opslog::SurfaceFailure) {
ui.colored_label(crate::theme::ICHOR, format!("⚠ {}", failure.argv));
if !failure.stderr_tail.is_empty() {
ui.colored_label(crate::theme::ICHOR, &failure.stderr_tail);
}
}
fn entropy_seed() -> u64 {
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0, |d| d.as_nanos() as u64);
nanos ^ (u64::from(std::process::id()) << 32)
}
pub(crate) fn now_ts() -> String {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0, |d| d.as_secs())
.to_string()
}