use super::*;
use crate::components::InputKey;
use std::sync::atomic::Ordering;
impl EditorHook {
pub(super) fn toggle_console(&mut self, world: &mut World) {
self.console_open = !self.console_open;
if self.console_open {
self.console_focus = true;
self.console_pinned = true;
widget::seed_field(world, console_panel::INPUT, "");
if let Some(t) = widget::input_mut(world, console_panel::INPUT) {
t.max_len = 0;
}
self.focus_panel(PanelKey::Console);
} else {
self.console_focus = false;
}
}
pub(super) fn drive_console_toggle(&mut self, input: &FrameInput, world: &mut World) {
if input.captured_key != Some(InputKey::Backtick) || input.ctrl {
return;
}
if self.sim.playing() || self.non_console_text_focus() {
return;
}
self.toggle_console(world);
if self.console_open {
self.console_blur = true;
}
}
pub(super) fn console_window(&self) -> (Vec<console::ConsoleLine>, usize, usize) {
let shown = console_panel::visible_lines(self.effective_size(PanelKey::Console)[1]);
let total = self.console_sink.len();
let max_first = total.saturating_sub(shown);
let first = if self.console_pinned {
max_first
} else {
self.console_scroll.min(max_first)
};
(self.console_sink.window(first, shown), total, first)
}
pub(super) fn make_console_view<'a>(
&self,
lines: &'a [console::ConsoleLine],
total: usize,
first: usize,
ghost: &'a str,
mouse: [f32; 2],
) -> ConsoleView<'a> {
ConsoleView {
lines,
total,
first,
focus: self.console_focus
&& !self.console_blur
&& self.panel_order.last() == Some(&PanelKey::Console),
ghost,
mouse,
}
}
pub(super) fn console_ghost(&self, world: &World) -> String {
let text = widget::field_text(world, console_panel::INPUT);
console::del_ghost(&text, self.entries.iter().filter_map(entry_name)).unwrap_or_default()
}
pub(super) fn scroll_console(&mut self, delta: f32) {
let shown = console_panel::visible_lines(self.effective_size(PanelKey::Console)[1]);
let max = self.console_sink.len().saturating_sub(shown);
let cur = if self.console_pinned {
max
} else {
self.console_scroll.min(max)
};
let next = scroll_step(cur, delta, max);
self.console_scroll = next;
self.console_pinned = next >= max;
}
pub(super) fn apply_console_action(&mut self, action: ConsoleAction, _world: &mut World) {
match action {
ConsoleAction::FocusInput => self.console_focus = true,
ConsoleAction::Consume => self.console_focus = false,
}
}
pub(super) fn console_keys(&mut self, world: &mut World, input: &FrameInput) {
if !self.console_focus {
return;
}
match input.captured_key {
Some(InputKey::Enter) => {
let line = widget::field_text(world, console_panel::INPUT);
widget::seed_field(world, console_panel::INPUT, "");
let line = line.trim().to_string();
if !line.is_empty() {
self.run_console_line(world, &line);
}
}
Some(InputKey::Tab) => self.accept_console_ghost(world),
Some(InputKey::Right) => {
let at_end = widget::input(world, console_panel::INPUT)
.map(|t| t.caret >= t.content.chars().count())
.unwrap_or(false);
if at_end {
self.accept_console_ghost(world);
}
}
_ => {}
}
}
fn accept_console_ghost(&mut self, world: &mut World) {
let text = widget::field_text(world, console_panel::INPUT);
if let Some(ghost) = console::del_ghost(&text, self.entries.iter().filter_map(entry_name)) {
widget::focus_field_with(world, console_panel::INPUT, &format!("{text}{ghost}"));
}
}
pub(super) fn run_console_line(&mut self, world: &mut World, line: &str) {
self.console_sink
.push(console::Severity::Command, &format!("> {line}"));
self.console_pinned = true;
match console::parse_command(line) {
Ok(console::Command::Echo(_)) => {}
Ok(console::Command::Add { target, name }) => {
self.console_add(&target, name.as_deref());
}
Ok(console::Command::Del { name }) => self.console_del(&name),
Ok(console::Command::Cook) => self.console_build(),
Ok(console::Command::Snap(cmd)) => self.console_snap(cmd),
Ok(console::Command::Dup) => {
let made = self.duplicate_selection();
self.console_sink.info(&format!("duplicated {made}"));
}
Ok(console::Command::Floor) => {
let moved = self.drop_selection_to_floor(world);
self.console_sink.info(&format!("dropped {moved}"));
}
Ok(console::Command::Select(cmd)) => self.console_select(cmd, world),
Ok(console::Command::Export { name, bake }) => {
self.console_export(name.as_deref(), bake);
}
Ok(console::Command::Help) => {
for l in console::help_lines() {
self.console_sink.info(&l);
}
}
Err(e) => self.console_sink.error(&e),
}
}
fn console_add(&mut self, target: &str, name: Option<&str>) {
if crate::authoring::is_path_like(target) && !std::path::Path::new(target).is_file() {
self.console_sink.error(&format!("{target}: no such file"));
return;
}
let mut new_entries = match crate::authoring::resolve_add_target(target) {
Ok(entries) => entries,
Err(e) => {
self.console_sink.error(&e.to_string());
return;
}
};
if let Some(n) = name {
crate::authoring::apply_name_override(&mut new_entries, n);
}
if let Some((name, source)) =
crate::authoring::try_retarget_environment_map(&mut self.entries, &new_entries)
{
self.console_sink.info(&format!("{name} now uses {source}"));
self.mark_changed();
return;
}
for entry in &mut new_entries {
let base = entry_name(entry).unwrap_or("asset").to_string();
let unique = self.unique_from(&base);
entry["name"] = serde_json::Value::String(unique.clone());
let ty = entry_type(entry).unwrap_or("?").to_string();
self.entries.push(entry.clone());
self.console_sink.info(&format!("added '{unique}' ({ty})"));
}
self.mark_changed();
}
fn console_del(&mut self, name: &str) {
let Some(idx) = self
.entries
.iter()
.position(|e| entry_name(e) == Some(name))
else {
self.console_sink
.error(&format!("no authored asset named '{name}'"));
return;
};
let ty = entry_type(&self.entries[idx]).unwrap_or("?").to_string();
self.entries.remove(idx);
self.mark_changed();
match self.form_target {
FormTarget::Entry(e) if e == idx => self.close_form(),
FormTarget::Entry(e) if e > idx => self.form_target = FormTarget::Entry(e - 1),
_ => {}
}
self.row_menu = None;
self.console_sink.info(&format!("removed '{name}' ({ty})"));
}
fn console_snap(&mut self, cmd: console::SnapCmd) {
use console::{SnapCmd, SnapSet};
fn set(snap: &mut snap::Snap, s: SnapSet) {
match s {
SnapSet::Enable(on) => snap.enabled = on,
SnapSet::Step(step) => {
snap.step = step;
snap.enabled = true;
}
}
}
match cmd {
SnapCmd::Status => {}
SnapCmd::All(on) => {
self.snap.translate.enabled = on;
self.snap.rotate.enabled = on;
}
SnapCmd::Move(s) => set(&mut self.snap.translate, s),
SnapCmd::Rotate(s) => set(&mut self.snap.rotate, s),
}
self.console_sink.info(&self.snap.describe());
}
fn console_build(&mut self) {
if self.console_build_running.swap(true, Ordering::SeqCst) {
self.console_sink.warn("cook already running");
return;
}
let content = match crate::world::write_world_jsonl(&self.entries) {
Ok(c) => c,
Err(e) => {
self.console_build_running.store(false, Ordering::SeqCst);
self.console_sink.error(&format!("cook failed: {e}"));
return;
}
};
let sink = self.console_sink.clone();
let toasts = self.notifier.clone();
sink.info("cook started");
self.spawn_cook_worker("Cooking", content, move |outcome, secs| match outcome {
Ok(()) => {
sink.info(&format!("cook finished in {secs:.1}s"));
toasts.success(&format!("Cook finished in {secs:.1}s"));
}
Err(e) => {
sink.error(&format!("cook failed: {e}"));
toasts.error_with(&format!("Cook failed: {e}"), notify::Action::OpenConsole);
}
});
}
}