Feat: add hidden mode and new documentation
This commit is contained in:
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>() {
|
||||
|
||||
@@ -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
69
src/input/script_page.rs
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user