Feat: add hidden mode and new documentation

This commit is contained in:
2026-02-26 12:31:56 +01:00
parent e1cf57918e
commit 70032acc75
95 changed files with 1055 additions and 286 deletions

View File

@@ -8,6 +8,7 @@ mod mouse;
pub(crate) mod options_page;
mod panel;
mod patterns_page;
mod script_page;
use arc_swap::ArcSwap;
use crossbeam_channel::Sender;
@@ -85,6 +86,7 @@ pub fn handle_key(ctx: &mut InputContext, key: KeyEvent) -> InputResult {
fn handle_live_keys(ctx: &mut InputContext, key: &KeyEvent) -> bool {
match (key.code, key.kind) {
_ if !matches!(ctx.app.ui.modal, Modal::None) => false,
_ if ctx.app.page == Page::Script && ctx.app.script_editor.focused => false,
(KeyCode::Char('f'), KeyEventKind::Press) => {
ctx.dispatch(AppCommand::ToggleLiveKeysFill);
true
@@ -134,6 +136,7 @@ fn handle_normal_input(ctx: &mut InputContext, key: KeyEvent) -> InputResult {
KeyCode::F(4) => Some(Page::Help),
KeyCode::F(5) => Some(Page::Main),
KeyCode::F(6) => Some(Page::Engine),
KeyCode::F(7) => Some(Page::Script),
_ => None,
} {
ctx.app.ui.minimap = MinimapMode::Timed(Instant::now() + Duration::from_millis(250));
@@ -148,6 +151,7 @@ fn handle_normal_input(ctx: &mut InputContext, key: KeyEvent) -> InputResult {
Page::Options => options_page::handle_options_page(ctx, key),
Page::Help => help_page::handle_help_page(ctx, key),
Page::Dict => help_page::handle_dict_page(ctx, key),
Page::Script => script_page::handle_script_page(ctx, key),
}
}

View File

@@ -6,7 +6,7 @@ use crate::engine::SeqCommand;
use crate::model::{FollowUp, PatternSpeed};
use crate::state::{
ConfirmAction, EditorTarget, EuclideanField, Modal, PatternField,
PatternPropsField, RenameTarget,
PatternPropsField, RenameTarget, ScriptField,
};
pub(super) fn handle_modal_input(ctx: &mut InputContext, key: KeyEvent) -> InputResult {
@@ -141,6 +141,44 @@ pub(super) fn handle_modal_input(ctx: &mut InputContext, key: KeyEvent) -> Input
KeyCode::Char(c) => input.push(c),
_ => {}
},
Modal::SetScript { field, input } => match key.code {
KeyCode::Enter => {
let field = *field;
match field {
ScriptField::Length => {
if let Ok(len) = input.parse::<usize>() {
ctx.dispatch(AppCommand::SetScriptLength(len));
let new_len = ctx.app.project_state.project.script_length;
ctx.dispatch(AppCommand::SetStatus(format!(
"Script length set to {new_len}"
)));
} else {
ctx.dispatch(AppCommand::SetStatus("Invalid length".to_string()));
}
}
ScriptField::Speed => {
if let Some(speed) = PatternSpeed::from_label(input) {
ctx.dispatch(AppCommand::SetScriptSpeed(speed));
ctx.dispatch(AppCommand::SetStatus(format!(
"Script speed set to {}",
speed.label()
)));
} else {
ctx.dispatch(AppCommand::SetStatus(
"Invalid speed (try 1/3, 2/5, 1x, 2x)".to_string(),
));
}
}
}
ctx.dispatch(AppCommand::CloseModal);
}
KeyCode::Esc => ctx.dispatch(AppCommand::CloseModal),
KeyCode::Backspace => {
input.pop();
}
KeyCode::Char(c) => input.push(c),
_ => {}
},
Modal::JumpToStep(input) => match key.code {
KeyCode::Enter => {
if let Ok(step) = input.parse::<usize>() {

View File

@@ -7,7 +7,7 @@ use crate::state::{
DeviceKind, DictFocus, EngineSection, HelpFocus, MinimapMode, Modal, OptionsFocus,
PatternsColumn, SettingKind,
};
use crate::views::{dict_view, engine_view, help_view, main_view, patterns_view};
use crate::views::{dict_view, engine_view, help_view, main_view, patterns_view, script_view};
use super::InputContext;
@@ -28,9 +28,11 @@ pub fn handle_mouse(ctx: &mut InputContext, mouse: MouseEvent, term: Rect) {
MouseEventKind::Down(MouseButton::Left) => handle_click(ctx, col, row, term),
MouseEventKind::Drag(MouseButton::Left) | MouseEventKind::Moved => {
handle_editor_drag(ctx, col, row, term);
handle_script_editor_drag(ctx, col, row, term);
}
MouseEventKind::Up(MouseButton::Left) => {
ctx.app.editor_ctx.mouse_selecting = false;
ctx.app.script_editor.mouse_selecting = false;
}
MouseEventKind::ScrollUp => handle_scroll(ctx, col, row, term, true),
MouseEventKind::ScrollDown => handle_scroll(ctx, col, row, term, false),
@@ -176,6 +178,14 @@ fn handle_scroll(ctx: &mut InputContext, col: u16, row: u16, term: Rect, up: boo
ctx.dispatch(AppCommand::StepDown);
}
}
Page::Script => {
let [editor_area, _] = script_view::layout(body);
if contains(editor_area, col, row) {
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
let code = if up { KeyCode::Up } else { KeyCode::Down };
ctx.app.script_editor.editor.input(KeyEvent::new(code, KeyModifiers::empty()));
}
}
Page::Help => {
let [topics_area, content_area] = help_view::layout(body);
if contains(topics_area, col, row) {
@@ -305,6 +315,7 @@ fn handle_footer_click(ctx: &mut InputContext, col: u16, row: u16, footer: Rect)
Page::Options => " OPTIONS ",
Page::Help => " HELP ",
Page::Dict => " DICT ",
Page::Script => " SCRIPT ",
};
let badge_end = block_inner.x + badge_text.len() as u16;
if col < badge_end {
@@ -345,6 +356,7 @@ fn handle_body_click(ctx: &mut InputContext, col: u16, row: u16, body: Rect) {
Page::Dict => handle_dict_click(ctx, col, row, page_area),
Page::Options => handle_options_click(ctx, col, row, page_area),
Page::Engine => handle_engine_click(ctx, col, row, page_area),
Page::Script => handle_script_click(ctx, col, row, page_area),
}
}
@@ -697,6 +709,84 @@ fn handle_options_click(ctx: &mut InputContext, col: u16, row: u16, area: Rect)
// --- Engine page ---
fn handle_script_click(ctx: &mut InputContext, col: u16, row: u16, area: Rect) {
let [editor_area, _] = script_view::layout(area);
if contains(editor_area, col, row) {
ctx.app.script_editor.focused = true;
handle_script_editor_mouse(ctx, col, row, area, false);
} else {
ctx.app.script_editor.focused = false;
}
}
fn script_editor_text_area(area: Rect) -> Rect {
let [editor_area, _] = script_view::layout(area);
// Block with borders → inner
let inner = Rect {
x: editor_area.x + 1,
y: editor_area.y + 1,
width: editor_area.width.saturating_sub(2),
height: editor_area.height.saturating_sub(2),
};
// Editor takes all but last row (hint line)
let editor_height = inner.height.saturating_sub(1);
Rect::new(inner.x, inner.y, inner.width, editor_height)
}
fn handle_script_editor_drag(ctx: &mut InputContext, col: u16, row: u16, term: Rect) {
if ctx.app.script_editor.mouse_selecting {
let padded = padded(term);
let (_header, body, _footer) = top_level_layout(padded);
let page_area = if ctx.app.panel.visible && ctx.app.panel.side.is_some() {
if body.width >= 120 {
let panel_width = body.width * 35 / 100;
Layout::horizontal([Constraint::Fill(1), Constraint::Length(panel_width)])
.split(body)[0]
} else {
let panel_height = body.height * 40 / 100;
Layout::vertical([Constraint::Fill(1), Constraint::Length(panel_height)])
.split(body)[0]
}
} else {
body
};
handle_script_editor_mouse(ctx, col, row, page_area, true);
}
}
fn handle_script_editor_mouse(
ctx: &mut InputContext,
col: u16,
row: u16,
area: Rect,
dragging: bool,
) {
let text_area = script_editor_text_area(area);
if col < text_area.x
|| col >= text_area.x + text_area.width
|| row < text_area.y
|| row >= text_area.y + text_area.height
{
return;
}
let scroll = ctx.app.script_editor.editor.scroll_offset();
let text_row = (row - text_area.y) + scroll;
let text_col = col - text_area.x;
if dragging {
if !ctx.app.script_editor.editor.is_selecting() {
ctx.app.script_editor.editor.start_selection();
}
} else {
ctx.app.script_editor.mouse_selecting = true;
ctx.app.script_editor.editor.cancel_selection();
}
ctx.app.script_editor.editor.move_cursor_to(text_row, text_col);
}
fn handle_engine_click(ctx: &mut InputContext, col: u16, row: u16, area: Rect) {
let [left_col, _, _] = engine_view::layout(area);

69
src/input/script_page.rs Normal file
View File

@@ -0,0 +1,69 @@
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use std::sync::atomic::Ordering;
use crate::commands::AppCommand;
use crate::state::{ConfirmAction, Modal, ScriptField};
use super::{InputContext, InputResult};
pub fn handle_script_page(ctx: &mut InputContext, key: KeyEvent) -> InputResult {
if ctx.app.script_editor.focused {
handle_focused(ctx, key)
} else {
handle_unfocused(ctx, key)
}
}
fn handle_focused(ctx: &mut InputContext, key: KeyEvent) -> InputResult {
let ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
match (ctrl, key.code) {
(_, KeyCode::Esc) => {
ctx.dispatch(AppCommand::ScriptSave);
ctx.app.script_editor.focused = false;
}
(true, KeyCode::Char('e')) => {
ctx.dispatch(AppCommand::ScriptSave);
ctx.dispatch(AppCommand::ScriptEvaluate);
}
(true, KeyCode::Char('s')) => {
ctx.dispatch(AppCommand::ToggleScriptStack);
}
_ => {
ctx.app.script_editor.editor.input(key);
}
}
InputResult::Continue
}
fn handle_unfocused(ctx: &mut InputContext, key: KeyEvent) -> InputResult {
match key.code {
KeyCode::Enter => {
ctx.app.script_editor.focused = true;
}
KeyCode::Char('q') if !ctx.app.plugin_mode => {
ctx.dispatch(AppCommand::OpenModal(Modal::Confirm {
action: ConfirmAction::Quit,
selected: false,
}));
}
KeyCode::Char(' ') if !ctx.app.plugin_mode => {
ctx.dispatch(AppCommand::TogglePlaying);
ctx.playing
.store(ctx.app.playback.playing, Ordering::Relaxed);
}
KeyCode::Char('s') => super::open_save(ctx),
KeyCode::Char('l') => super::open_load(ctx),
KeyCode::Char('?') => {
ctx.dispatch(AppCommand::OpenModal(Modal::KeybindingsHelp { scroll: 0 }));
}
KeyCode::Char('L') => {
ctx.dispatch(AppCommand::OpenScriptModal(ScriptField::Length));
}
KeyCode::Char('S') => {
ctx.dispatch(AppCommand::OpenScriptModal(ScriptField::Speed));
}
_ => {}
}
InputResult::Continue
}